microsoft/FASTER · error · FasterException

Cannot scan disposed log instance

Error message

Cannot scan disposed log instance

What it means

FasterLog uses a reference count (logRefCount) on the underlying log; a value of 0 after increment means the log instance has already been disposed. Scan therefore refuses to create an iterator on a disposed instance at FasterLog.cs:1842 to avoid operating on freed devices/memory.

Solutions

  1. Ensure the log instance is not disposed before all Scan calls finish; keep logRefCount held via a live reference.
  2. Fix shutdown ordering: cancel/await iterator consumer tasks before disposing the FasterLog.
  3. If the instance was disposed intentionally, create a new FasterLog from the same settings instead of scanning the old one.

Example fix

// before
log.Dispose();
var iter = log.Scan(0, long.MaxValue);
// after
var iter = log.Scan(0, long.MaxValue);
// consume...
iter.Dispose();
log.Dispose();
Defensive patterns

Strategy: try-catch

Validate before calling

// track disposal in your wrapper
if (Interlocked.Read(ref disposed) != 0) throw new ObjectDisposedException(nameof(FasterLog));

Try / catch

try { var iter = log.Scan(0, long.MaxValue); } catch (FasterException ex) when (ex.Message.Contains("disposed log instance")) { // recreate log or stop consuming
}

Prevention

When it happens

Trigger: Calling FasterLog.Scan (with or without a name) after the log's Dispose() has been called, or concurrently racing Dispose against Scan while other references have been released.

Common situations: Application shutdown ordering issues where a consumer thread still calls Scan after the hosting service disposed the FasterLog; or reusing a cached disposed FasterLog singleton.

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/99ae559958d916b5. Report an issue: GitHub.

Appendix: source

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

                throw new FasterException("Cannot use scanUncommitted without setting AutoRefreshSafeTailAddress to true in FasterLog settings");

            FasterLogScanIterator iter;
            if (recover && name != null && RecoveredIterators != null && RecoveredIterators.ContainsKey(name))
                iter = new FasterLogScanIterator(this, allocator, RecoveredIterators[name], endAddress, getMemory, scanBufferingMode, epoch, headerSize, name, scanUncommitted, logger: logger);
            else
                iter = new FasterLogScanIterator(this, allocator, beginAddress, endAddress, getMemory, scanBufferingMode, epoch, headerSize, name, scanUncommitted, logger: logger);

            if (name != null)
            {
                if (name.Length > 20)
                    throw new FasterException("Max length of iterator name is 20 characters");
                if (PersistedIterators.ContainsKey(name))
                    logger?.LogDebug("Iterator name exists, overwriting");
                PersistedIterators[name] = iter;
            }

            if (Interlocked.Increment(ref logRefCount) == 1)
                throw new FasterException("Cannot scan disposed log instance");
            return iter;
        }

        /// <summary>
        /// Random read record from log, at given address
        /// </summary>
        /// <param name="address">Logical address to read from</param>
        /// <param name="estimatedLength">Estimated length of entry, if known</param>
        /// <param name="token">Cancellation token</param>
        /// <returns></returns>
        public async ValueTask<(byte[], int)> ReadAsync(long address, int estimatedLength = 0, CancellationToken token = default)
        {
            token.ThrowIfCancellationRequested();
            epoch.Resume();
            if (address >= CommittedUntilAddress || address < BeginAddress)
            {
                epoch.Suspend();
                return default;

View on GitHub (pinned to 321d872eab)