apache/cassandra · error · IllegalStateException

Can't revert join from

Error message

Can't revert join from 

What it means

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.

Source

Thrown at src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java:342

        return new ProgressBarrier(latestModification, metadata.directory.location(startJoin.nodeId()), metadata.lockedRanges.locked.get(lockKey));
    }

    @Override
    public ClusterMetadata.Transformer cancel(ClusterMetadata metadata)
    {
        DataPlacements placements = metadata.placements();
        switch (next)
        {
            // need to undo MID_JOIN and START_JOIN, then merge the ranges split by PrepareJoin
            case FINISH_JOIN:
                placements = midJoin.inverseDelta().apply(metadata.directory, metadata.nextEpoch(), placements);
            case MID_JOIN:
                placements = startJoin.inverseDelta().apply(metadata.directory, metadata.nextEpoch(), placements);
            case START_JOIN:
                placements = toSplitRanges.invert().apply(metadata.directory, metadata.nextEpoch(), placements);
                break;
            default:
                throw new IllegalStateException("Can't revert join from " + next);
        }
        LockedRanges newLockedRanges = metadata.lockedRanges.unlock(lockKey);
        return metadata.transformer()
                       .withNodeState(startJoin.nodeId(), NodeState.REGISTERED)
                       .with(placements)
                       .with(newLockedRanges);
    }

    public BootstrapAndJoin finishJoiningRing()
    {
        return new BootstrapAndJoin(latestModification, lockKey, toSplitRanges,
                                    next, startJoin, midJoin, finishJoin,
                                    true, false);
    }

    @VisibleForTesting
    public Pair<MovementMap, MovementMap> getMovementMaps(ClusterMetadata metadata)
    {

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. 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
  2. If cancellation raced and the join completed, treat the node as joined and run decommission to remove it
  3. Re-read ClusterMetadata to get the authoritative node state and re-issue the cancellation against the current sequence
  4. Report/log this as an unexpected state if next is neither of the handled kinds, since it may indicate metadata corruption

Example fix

// before
bootstrapAndJoin.cancel(metadata);
// after
if (bootstrapAndJoin.nextStep() == Transformation.Kind.FINISH_JOIN)
    // join already committed; use decommission instead
else
    bootstrapAndJoin.cancel(metadata);
Defensive patterns

Strategy: try-catch

Validate before calling

Transformation.Kind step = sequence.nextStep();
if (step != Transformation.Kind.START_JOIN && step != Transformation.Kind.MID_JOIN)
    throw new IllegalStateException("cannot revert join from step " + step);

Try / catch

try { seq.cancel(metadata); } catch (IllegalStateException e) { /* if FINISH_JOIN: treat as joined, run decommission instead */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/0c17e14ab99b2612. Report an issue: GitHub.