{"record":{"id":"8583946c378af536","repo":"apache/cassandra","slug":"attempted-to-release-storage-attached-index-segmen","errorCode":null,"errorMessage":"Attempted to release storage-attached index segment builder memory after builder marked inactive.","messagePattern":"Attempted to release storage-attached index segment builder memory after builder marked inactive\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/java/org/apache/cassandra/index/sai/disk/v1/segment/SegmentBuilder.java","lineNumber":242,"sourceCode":"     * This method does three things:\n     * <p>\n     * 1. It decrements active builder count and updates the global minimum flush size to reflect that.\n     * 2. It releases the builder's memory against its limiter.\n     * 3. It defensively marks the builder inactive to make sure nothing bad happens if we try to close it twice.\n     *\n     * @return the number of bytes used by the memory limiter after releasing this builder\n     */\n    public long release()\n    {\n        if (active)\n        {\n            minimumFlushBytes = limiter.limitBytes() / ACTIVE_BUILDER_COUNT.getAndDecrement();\n            long used = limiter.decrement(totalBytesAllocated);\n            active = false;\n            return used;\n        }\n\n        logger.warn(index.identifier().logMessage(\"Attempted to release storage-attached index segment builder memory after builder marked inactive.\"));\n        return limiter.currentBytesUsed();\n    }\n\n    public abstract boolean isEmpty();\n\n    protected abstract long addInternal(ByteBuffer term, int segmentRowId);\n\n    protected abstract SegmentMetadata.ComponentMetadataMap flushInternal(IndexDescriptor indexDescriptor) throws IOException;\n\n    public int getRowCount()\n    {\n        return rowCount;\n    }\n\n    /**\n     * @return true if next SSTable row ID exceeds max segment row ID\n     */\n    public boolean exceedsSegmentLimit(long ssTableRowId)","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/index/sai/disk/v1/segment/SegmentBuilder.java#L224-L260","documentation":"This warning is logged by SegmentBuilder.release when release() is called on a builder whose active flag is already false, meaning its memory was already released against the SAI memory limiter and ACTIVE_BUILDER_COUNT was already decremented. The call is a no-op (it just returns the limiter's current usage), but it signals a double-release or a release after the builder was already finalized during flush. Memory accounting remains correct; the warning is a lifecycle-management bug signal.","triggerScenarios":"Calling release() twice on the same SegmentBuilder, or calling release() after flush completed and the builder was already marked inactive; typically from index builder cleanup paths racing with the flush-and-release sequence.","commonSituations":"A failed or retried flush path where cleanup code runs both in an error handler and in the normal close path; concurrent index drops during flush causing duplicate release calls; custom patches or upgrades where the builder lifecycle ordering changed.","solutions":["Guard the release call with an ownership/flag in the caller so release() runs exactly once (or rely on the builder's own active flag as the guard)","Audit the flush/close path so release happens in a single finally block per builder rather than from multiple cleanup sites","If reproducible after an upgrade, rebuild the index and report with a thread dump/stack trace to the Cassandra community as a possible lifecycle bug"],"exampleFix":"// before\nsegmentBuilder.release();\nsegmentBuilder.release(); // double release in error path\n// after\nif (segmentBuilder != null && released.compareAndSet(false, true))\n    segmentBuilder.release();","handlingStrategy":"validation","validationCode":"// check before releasing\nif (segmentBuilder.isActive())\n    segmentBuilder.release();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call release() exactly once per builder, from a single owner in a finally block","Never call release() after flush()/close() has completed","Add an idempotency flag in caller code when multiple cleanup paths exist","Watch SAI memory-limiter accounting in logs; repeated double-release warnings indicate a lifecycle bug to fix"],"tags":["sai","index","lifecycle","memory-leak"],"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"}