{"record":{"id":"32a7985257cc917b","repo":"apache/cassandra","slug":"only-transition-from-forbidden-to-permitted-is-all","errorCode":null,"errorMessage":"Only transition from FORBIDDEN to PERMITTED is allowed.","messagePattern":"Only transition from FORBIDDEN to PERMITTED is allowed\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java","lineNumber":697,"sourceCode":"     * Change the current cdcState on this CommitLogSegment. There are some restrictions on state transitions and this\n     * method is idempotent.\n     *\n     * @return the old cdc state\n     */\n    public CDCState setCDCState(CDCState newState)\n    {\n        if (newState == cdcState)\n            return cdcState;\n\n        // Also synchronized in CDCSizeTracker.processNewSegment and .processDiscardedSegment\n        synchronized(cdcStateLock)\n        {\n            // Need duplicate CONTAINS to be idempotent since 2 threads can race on this lock\n            if (cdcState == CDCState.CONTAINS && newState != CDCState.CONTAINS)\n                throw new IllegalArgumentException(\"Cannot transition from CONTAINS to any other state.\");\n\n            if (cdcState == CDCState.FORBIDDEN && newState != CDCState.PERMITTED)\n                throw new IllegalArgumentException(\"Only transition from FORBIDDEN to PERMITTED is allowed.\");\n\n            CDCState oldState = cdcState;\n            cdcState = newState;\n            return oldState;\n        }\n    }\n\n    /**\n     * A simple class for tracking information about the portion of a segment that has been allocated to a log write.\n     */\n    protected static class Allocation\n    {\n        private final CommitLogSegment segment;\n        private final OpOrder.Group appendOp;\n        private final int position;\n        private final ByteBuffer buffer;\n\n        Allocation(CommitLogSegment segment, OpOrder.Group appendOp, int position, ByteBuffer buffer)","sourceCodeStart":679,"sourceCodeEnd":715,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/db/commitlog/CommitLogSegment.java#L679-L715","documentation":"The other half of the CDC state machine: a segment in FORBIDDEN state (CDC disk quota exceeded, writes blocked) may only transition back to PERMITTED. Any other target state throws IllegalArgumentException, preventing direct jumps like FORBIDDEN->CONTAINS that would bypass the permit cycle.","triggerScenarios":"Calling setCDCState(FORBIDDEN->CONTAINS) or FORBIDDEN->FORBIDDEN is fine but e.g. setCDCState with newState != PERMITTED while cdcState == FORBIDDEN, typically from custom CDC quota logic or tests.","commonSituations":"Custom cdc_free_space_in_mb handling code forcing states; tests simulating quota blocking and forgetting to return via PERMITTED; races where allocation attempts to mark CONTAINS on a blocked segment.","solutions":["Always restore a FORBIDDEN segment through PERMITTED before any other transition.","Let CommitLog's own setCDCBlockWrites/quota machinery drive FORBIDDEN/PERMITTED changes instead of manual calls.","Inspect current state with getCDCState() and implement a transition map honoring FORBIDDEN->PERMITTED only.","Check cdc_free_space_in_mb sizing so quota logic (not state misuse) resolves the block."],"exampleFix":"// before\nsegment.setCDCState(CDCState.CONTAINS); // segment FORBIDDEN\n// after\nsegment.setCDCState(CDCState.PERMITTED); // required hop\nsegment.setCDCState(CDCState.CONTAINS);","handlingStrategy":"validation","validationCode":"if (segment.getCDCState() == CDCState.FORBIDDEN && newState != CDCState.PERMITTED)\n    throw new IllegalArgumentException(\"FORBIDDEN may only transition to PERMITTED\");","typeGuard":null,"tryCatchPattern":"try {\n    segment.setCDCState(newState);\n} catch (IllegalArgumentException e) {\n    logger.warn(\"Blocked CDC transition (FORBIDDEN requires PERMITTED): {}\", e.getMessage());\n}","preventionTips":["Route FORBIDDEN recovery exclusively through PERMITTED","Use CommitLog.setCDCBlockWrites rather than direct state writes","Size cdc_free_space_in_mb so quota unblocking occurs normally"],"tags":["commitlog","cdc","state-machine","quota"],"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-17T15:17:12.973Z"}