{"record":{"id":"93cc90c02dad4ac0","repo":"apache/cassandra","slug":"cannot-move-to-unregistered-state-s","errorCode":null,"errorMessage":"Cannot move to UNREGISTERED state (%s)","messagePattern":"Cannot move to UNREGISTERED state \\((.+?)\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/RegistrationStatus.java","lineNumber":53,"sourceCode":"    public static final RegistrationStatus instance = new RegistrationStatus();\n    private final AtomicReference<RegistrationStatus.State> state = new AtomicReference<>(State.INITIAL);\n\n    public RegistrationStatus.State getCurrent()\n    {\n        return state.get();\n    }\n\n    @VisibleForTesting\n    public void resetState()\n    {\n        state.set(State.INITIAL);\n    }\n\n    public void onInitialized()\n    {\n        logger.info(\"Node is initialized, moving to UNREGISTERED state\");\n        if (!state.compareAndSet(State.INITIAL, State.UNREGISTERED))\n            throw new IllegalStateException(String.format(\"Cannot move to UNREGISTERED state (%s)\", state.get()));\n    }\n\n    public void onRegistration()\n    {\n        // This may have been done already if the metadata log replay at start up included our registration\n        RegistrationStatus.State current = state.get();\n        if (current == State.REGISTERED)\n            return;\n\n        logger.info(\"This node is registered, moving state to REGISTERED and interrupting any previously established peer connections\");\n        state.getAndSet(RegistrationStatus.State.REGISTERED);\n        MessagingService.instance().channelManagers.keySet().forEach(MessagingService.instance()::interruptOutbound);\n    }\n}\n","sourceCodeStart":35,"sourceCodeEnd":68,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/RegistrationStatus.java#L35-L68","documentation":"RegistrationStatus.onInitialized() atomically moves the node's registration state machine from INITIAL to UNREGISTERED using compareAndSet. If the current state is anything other than INITIAL (e.g. already UNREGISTERED, REGISTERED, or REGISTERING) the CAS fails and this IllegalStateException is thrown. It indicates the initialization callback fired twice or the state advanced concurrently, so the expected lifecycle transition is invalid.","triggerScenarios":"onInitialized() called when the state machine is no longer in State.INITIAL; concurrent/duplicate invocation of the initialization hook; replayed startup path already advanced the state to UNREGISTERED or REGISTERED before onInitialized ran.","commonSituations":"Double startup or repeated TCM initialization in the same JVM (common in tests); a race between startup hooks; replay of the metadata log containing the node's registration before the initialization callback.","solutions":["Log/inspect the current state at the throw site to determine which transition already ran.","Ensure onInitialized() is invoked exactly once per process startup.","If the registration was already replayed from the log, use onRegistration()'s idempotent path rather than forcing onInitialized().","In tests, create a fresh RegistrationStatus per test instead of reusing an instance across startup attempts."],"exampleFix":"// before: unconditional callback during double startup\nregistrationStatus.onInitialized();\n// after: guard on the expected state\nif (registrationStatus.state() == RegistrationStatus.State.INITIAL)\n    registrationStatus.onInitialized();","handlingStrategy":"type-guard","validationCode":"if (status.state() != RegistrationStatus.State.INITIAL) {\n    // skip onInitialized; state already advanced\n}","typeGuard":"boolean canInitialize(RegistrationStatus s) { return s.state() == RegistrationStatus.State.INITIAL; }","tryCatchPattern":"try { status.onInitialized(); }\ncatch (IllegalStateException e) {\n    if (e.getMessage().startsWith(\"Cannot move to UNREGISTERED\")) {\n        logger.info(\"Registration already advanced past INITIAL: {}\", e.getMessage());\n    } else throw e;\n}","preventionTips":["Invoke startup hooks exactly once per process","Make lifecycle transitions idempotent before calling CAS-based setters","Guard callbacks with state checks in test harnesses"],"tags":["tcm","state-machine","concurrency","startup"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}