{"record":{"id":"498157ae99df079c","repo":"microsoft/FASTER","slug":"log-has-already-been-closed","errorCode":null,"errorMessage":"log has already been closed","messagePattern":"log has already been closed","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"error","filePath":"cs/src/core/FasterLog/FasterLog.cs","lineNumber":2733,"sourceCode":"            };\n            info.SnapshotIterators(PersistedIterators);\n            var commitRequired = ShouldCommmitMetadata(ref info) || (commitCoveredAddress < TailAddress);\n            // Only apply commit policy if not a strong commit\n            if (fastForwardAllowed && !commitPolicy.AdmitCommit(TailAddress, commitRequired))\n                return false;\n\n            // This critical section serializes commit record creation / commit content generation and ensures that the\n            // long address are sorted in outstandingCommitRecords. Ok because we do not expect heavy contention on the\n            // commit code path\n            lock (ongoingCommitRequests)\n            {\n                if (commitCoveredAddress == TailAddress && !commitRequired)\n                    // Nothing to commit if no metadata update and no new entries\n                    return false;\n                if (commitNum == long.MaxValue)\n                {\n                    // log has been closed, throw an exception\n                    throw new FasterException(\"log has already been closed\");\n                }\n\n                // Make sure we will not be allowed to back out of a commit if AdmitCommit returns true, as the commit policy\n                // may need to update internal logic for every true response. We might waste some commit nums if commit\n                // policy filters out a lot of commits, but that's fine.\n                if (proposedCommitNum == -1)\n                    info.CommitNum = actualCommitNum = ++commitNum;\n                else if (proposedCommitNum > commitNum)\n                    info.CommitNum = actualCommitNum = commitNum = proposedCommitNum;\n                else\n                    // Invalid commit num\n                    return false;\n\n                // Normally --- only need commit records if fast committing.\n                if (fastCommitMode)\n                {\n                    // Ok to retry in critical section, any concurrently invoked commit would block, but cannot progress\n                    // anyways if no record can be enqueued","sourceCodeStart":2715,"sourceCodeEnd":2751,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/FasterLog/FasterLog.cs#L2715-L2751","documentation":"A commit was requested (CommitAsync path in FasterLog.cs:2733) after the log has been closed: the internal commitNum has been set to long.MaxValue during close/shutdown. The library throws rather than silently committing to a dead log.","triggerScenarios":"Calling CommitAsync after Dispose/Close on FasterLog, or racing a commit with shutdown (background commit task firing during Dispose).","commonSituations":"Application shutdown ordering: a producer task still committing while another thread disposes the log; failing to await in-flight commits before calling Dispose; restart-after-checkpoint reusing a disposed instance.","solutions":["Ensure all CommitAsync calls complete before disposing: await outstanding commits, then Dispose.","Guard commit calls with the log's lifecycle (check disposed/closed flag or a CancellationToken that is cancelled on shutdown).","Serialize shutdown: stop producers, drain, then dispose in one place rather than concurrently.","If it appears without explicit Dispose, look for an earlier exception that closed the log and handle that root cause."],"exampleFix":"// before\nlog.Dispose();\nawait producerTask; // producer may still CommitAsync -> throws\n\n// after\ncts.Cancel();          // signal producers to stop\nawait producerTask;    // drain in-flight commits\nlog.Dispose();","handlingStrategy":"try-catch","validationCode":"if (logDisposed) return; // track disposal in your wrapper","typeGuard":null,"tryCatchPattern":"try { await log.CommitAsync(); } catch (FasterException ex) when (ex.Message == \"log has already been closed\") { /* shutdown race: ignore */ }","preventionTips":["Cancel producers and drain commits before Dispose","Single owner thread handles shutdown","Guard with CancellationToken tied to shutdown"],"tags":["fasterlog","disposed","commit","shutdown"],"backgroundTag":"invalid-state-transition","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}