aeron-io/aeron · error · ClusterException

counter is closed

Error message

counter is closed

What it means

NodeControl.ToggleState.get(AtomicCounter) reads the cluster control-toggle counter and first requires it not be closed. A closed counter has no valid value, so reading it would be meaningless; a ClusterException is thrown instead.

Solutions

  1. Stop polling the control toggle once the cluster/agent is closed (track lifecycle)
  2. Check controlToggle.isClosed() before calling get and skip the read when closed
  3. Ensure the AtomicCounter is only closed after all readers have stopped
  4. If using in tooling, re-create the context/counters instead of reading stale ones

Example fix

// before
ToggleState state = NodeControl.ToggleState.get(controlToggle);
// after
if (controlToggle.isClosed()) { return; }
ToggleState state = NodeControl.ToggleState.get(controlToggle);
Defensive patterns

Strategy: type-guard

Validate before calling

if (controlToggle.isClosed()) return ToggleState.NEUTRAL;

Type guard

boolean readable(AtomicCounter c) { return c != null && !c.isClosed(); }

Try / catch

try { ts = NodeControl.ToggleState.get(controlToggle); } catch (ClusterException e) { ts = null; /* cluster closed */ }

Prevention

When it happens

Trigger: Calling ToggleState.get with an AtomicCounter that has already been closed — e.g. after the consensus module agent was closed, or while the cluster control toggle is being torn down during shutdown while another thread polls it.

Common situations: Monitoring/management code (e.g. ClusterTermination, snapshots control) polling the control toggle after cluster shutdown; lifecycle races during close.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/bc8784497d629481. Report an issue: GitHub.

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/NodeControl.java:138

         * @param controlToggle to be deactivated.
         */
        public static void deactivate(final AtomicCounter controlToggle)
        {
            controlToggle.set(INACTIVE.code());
        }

        /**
         * Get the {@link ToggleState} for a given control toggle.
         *
         * @param controlToggle to get the current state for.
         * @return the state for the current control toggle.
         * @throws ClusterException if the counter is not one of the valid values.
         */
        public static ToggleState get(final AtomicCounter controlToggle)
        {
            if (controlToggle.isClosed())
            {
                throw new ClusterException("counter is closed");
            }

            final long toggleValue = controlToggle.get();
            if (toggleValue < 0 || toggleValue > (STATES.length - 1))
            {
                throw new ClusterException("invalid toggle value: " + toggleValue);
            }

            return STATES[(int)toggleValue];
        }
    }

    /**
     * Counter type id for the control toggle.
     */
    public static final int CONTROL_TOGGLE_TYPE_ID = AeronCounters.NODE_CONTROL_TOGGLE_TYPE_ID;

    private NodeControl()

View on GitHub (pinned to 6d60124e15)