{"record":{"id":"0c17e14ab99b2612","repo":"apache/cassandra","slug":"can-t-revert-join-from","errorCode":null,"errorMessage":"Can't revert join from ","messagePattern":"Can't revert join from ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java","lineNumber":342,"sourceCode":"        return new ProgressBarrier(latestModification, metadata.directory.location(startJoin.nodeId()), metadata.lockedRanges.locked.get(lockKey));\n    }\n\n    @Override\n    public ClusterMetadata.Transformer cancel(ClusterMetadata metadata)\n    {\n        DataPlacements placements = metadata.placements();\n        switch (next)\n        {\n            // need to undo MID_JOIN and START_JOIN, then merge the ranges split by PrepareJoin\n            case FINISH_JOIN:\n                placements = midJoin.inverseDelta().apply(metadata.directory, metadata.nextEpoch(), placements);\n            case MID_JOIN:\n                placements = startJoin.inverseDelta().apply(metadata.directory, metadata.nextEpoch(), placements);\n            case START_JOIN:\n                placements = toSplitRanges.invert().apply(metadata.directory, metadata.nextEpoch(), placements);\n                break;\n            default:\n                throw new IllegalStateException(\"Can't revert join from \" + next);\n        }\n        LockedRanges newLockedRanges = metadata.lockedRanges.unlock(lockKey);\n        return metadata.transformer()\n                       .withNodeState(startJoin.nodeId(), NodeState.REGISTERED)\n                       .with(placements)\n                       .with(newLockedRanges);\n    }\n\n    public BootstrapAndJoin finishJoiningRing()\n    {\n        return new BootstrapAndJoin(latestModification, lockKey, toSplitRanges,\n                                    next, startJoin, midJoin, finishJoin,\n                                    true, false);\n    }\n\n    @VisibleForTesting\n    public Pair<MovementMap, MovementMap> getMovementMaps(ClusterMetadata metadata)\n    {","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java#L324-L360","documentation":"BootstrapAndJoin.cancel reverts a partially applied join sequence by inverting the transformations recorded so far. The switch over the sequence's current step (next) handles MID_JOIN/START_JOIN; IllegalStateException is thrown from the default branch when cancellation is attempted from any other state (e.g. FINISH_JOIN or an unexpected step), meaning the join cannot be reverted from that point.","triggerScenarios":"Calling cancel() on a BootstrapAndJoin whose Transformation.Kind state is not START_JOIN or MID_JOIN — e.g. the join already reached FINISH_JOIN, or the sequence object's state is uninitialized/corrupted.","commonSituations":"A user retries cancellation after the join already committed its final transformation; concurrent retries race so the second cancel observes a later state; a failed join plan leaves state at a kind the revert logic does not expect.","solutions":["Check the current step (nextStep()/state) before calling cancel and only revert from START_JOIN or MID_JOIN; if the join finished, use the appropriate removal/decommission sequence instead","If cancellation raced and the join completed, treat the node as joined and run decommission to remove it","Re-read ClusterMetadata to get the authoritative node state and re-issue the cancellation against the current sequence","Report/log this as an unexpected state if next is neither of the handled kinds, since it may indicate metadata corruption"],"exampleFix":"// before\nbootstrapAndJoin.cancel(metadata);\n// after\nif (bootstrapAndJoin.nextStep() == Transformation.Kind.FINISH_JOIN)\n    // join already committed; use decommission instead\nelse\n    bootstrapAndJoin.cancel(metadata);","handlingStrategy":"try-catch","validationCode":"Transformation.Kind step = sequence.nextStep();\nif (step != Transformation.Kind.START_JOIN && step != Transformation.Kind.MID_JOIN)\n    throw new IllegalStateException(\"cannot revert join from step \" + step);","typeGuard":null,"tryCatchPattern":"try { seq.cancel(metadata); } catch (IllegalStateException e) { /* if FINISH_JOIN: treat as joined, run decommission instead */ }","preventionTips":["Check the sequence's current step before attempting cancellation","Avoid concurrent/racing cancel invocations","Track join completion to switch cleanup strategy to decommission"],"tags":["tcm","bootstrap","join-revert","state-machine","illegal-state"],"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-14T16:17:12.679Z"}