microsoft/FASTER · error · FasterException

log has already been closed

Error message

log has already been closed

What it means

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.

Solutions

  1. Ensure all CommitAsync calls complete before disposing: await outstanding commits, then Dispose.
  2. Guard commit calls with the log's lifecycle (check disposed/closed flag or a CancellationToken that is cancelled on shutdown).
  3. Serialize shutdown: stop producers, drain, then dispose in one place rather than concurrently.
  4. If it appears without explicit Dispose, look for an earlier exception that closed the log and handle that root cause.

Example fix

// before
log.Dispose();
await producerTask; // producer may still CommitAsync -> throws

// after
cts.Cancel();          // signal producers to stop
await producerTask;    // drain in-flight commits
log.Dispose();
Defensive patterns

Strategy: try-catch

Validate before calling

if (logDisposed) return; // track disposal in your wrapper

Try / catch

try { await log.CommitAsync(); } catch (FasterException ex) when (ex.Message == "log has already been closed") { /* shutdown race: ignore */ }

Prevention

When it happens

Trigger: Calling CommitAsync after Dispose/Close on FasterLog, or racing a commit with shutdown (background commit task firing during Dispose).

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

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 microsoft/FASTER@321d872eab (2026-09-15). Data as JSON: /api/errors/498157ae99df079c. Report an issue: GitHub.

Appendix: source

Thrown at cs/src/core/FasterLog/FasterLog.cs:2733

            };
            info.SnapshotIterators(PersistedIterators);
            var commitRequired = ShouldCommmitMetadata(ref info) || (commitCoveredAddress < TailAddress);
            // Only apply commit policy if not a strong commit
            if (fastForwardAllowed && !commitPolicy.AdmitCommit(TailAddress, commitRequired))
                return false;

            // This critical section serializes commit record creation / commit content generation and ensures that the
            // long address are sorted in outstandingCommitRecords. Ok because we do not expect heavy contention on the
            // commit code path
            lock (ongoingCommitRequests)
            {
                if (commitCoveredAddress == TailAddress && !commitRequired)
                    // Nothing to commit if no metadata update and no new entries
                    return false;
                if (commitNum == long.MaxValue)
                {
                    // log has been closed, throw an exception
                    throw new FasterException("log has already been closed");
                }

                // Make sure we will not be allowed to back out of a commit if AdmitCommit returns true, as the commit policy
                // may need to update internal logic for every true response. We might waste some commit nums if commit
                // policy filters out a lot of commits, but that's fine.
                if (proposedCommitNum == -1)
                    info.CommitNum = actualCommitNum = ++commitNum;
                else if (proposedCommitNum > commitNum)
                    info.CommitNum = actualCommitNum = commitNum = proposedCommitNum;
                else
                    // Invalid commit num
                    return false;

                // Normally --- only need commit records if fast committing.
                if (fastCommitMode)
                {
                    // Ok to retry in critical section, any concurrently invoked commit would block, but cannot progress
                    // anyways if no record can be enqueued

View on GitHub (pinned to 321d872eab)