{"record":{"id":"bc8784497d629481","repo":"aeron-io/aeron","slug":"counter-is-closed-nodecontrol","errorCode":null,"errorMessage":"counter is closed","messagePattern":"counter is closed","errorType":"exception","errorClass":"ClusterException","httpStatus":null,"severity":"error","filePath":"aeron-cluster/src/main/java/io/aeron/cluster/NodeControl.java","lineNumber":138,"sourceCode":"         * @param controlToggle to be deactivated.\n         */\n        public static void deactivate(final AtomicCounter controlToggle)\n        {\n            controlToggle.set(INACTIVE.code());\n        }\n\n        /**\n         * Get the {@link ToggleState} for a given control toggle.\n         *\n         * @param controlToggle to get the current state for.\n         * @return the state for the current control toggle.\n         * @throws ClusterException if the counter is not one of the valid values.\n         */\n        public static ToggleState get(final AtomicCounter controlToggle)\n        {\n            if (controlToggle.isClosed())\n            {\n                throw new ClusterException(\"counter is closed\");\n            }\n\n            final long toggleValue = controlToggle.get();\n            if (toggleValue < 0 || toggleValue > (STATES.length - 1))\n            {\n                throw new ClusterException(\"invalid toggle value: \" + toggleValue);\n            }\n\n            return STATES[(int)toggleValue];\n        }\n    }\n\n    /**\n     * Counter type id for the control toggle.\n     */\n    public static final int CONTROL_TOGGLE_TYPE_ID = AeronCounters.NODE_CONTROL_TOGGLE_TYPE_ID;\n\n    private NodeControl()","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-cluster/src/main/java/io/aeron/cluster/NodeControl.java#L120-L156","documentation":"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.","triggerScenarios":"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.","commonSituations":"Monitoring/management code (e.g. ClusterTermination, snapshots control) polling the control toggle after cluster shutdown; lifecycle races during close.","solutions":["Stop polling the control toggle once the cluster/agent is closed (track lifecycle)","Check controlToggle.isClosed() before calling get and skip the read when closed","Ensure the AtomicCounter is only closed after all readers have stopped","If using in tooling, re-create the context/counters instead of reading stale ones"],"exampleFix":"// before\nToggleState state = NodeControl.ToggleState.get(controlToggle);\n// after\nif (controlToggle.isClosed()) { return; }\nToggleState state = NodeControl.ToggleState.get(controlToggle);","handlingStrategy":"type-guard","validationCode":"if (controlToggle.isClosed()) return ToggleState.NEUTRAL;","typeGuard":"boolean readable(AtomicCounter c) { return c != null && !c.isClosed(); }","tryCatchPattern":"try { ts = NodeControl.ToggleState.get(controlToggle); } catch (ClusterException e) { ts = null; /* cluster closed */ }","preventionTips":["Stop toggle polling before closing the agent","Check isClosed() on every read","Close counters only after readers stop"],"tags":["aeron-cluster","counter","closed-resource","lifecycle"],"backgroundTag":"resource-closed","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}