{"record":{"id":"99ae559958d916b5","repo":"microsoft/FASTER","slug":"cannot-scan-disposed-log-instance","errorCode":null,"errorMessage":"Cannot scan disposed log instance","messagePattern":"Cannot scan disposed log instance","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"error","filePath":"cs/src/core/FasterLog/FasterLog.cs","lineNumber":1842,"sourceCode":"                throw new FasterException(\"Cannot use scanUncommitted without setting AutoRefreshSafeTailAddress to true in FasterLog settings\");\n\n            FasterLogScanIterator iter;\n            if (recover && name != null && RecoveredIterators != null && RecoveredIterators.ContainsKey(name))\n                iter = new FasterLogScanIterator(this, allocator, RecoveredIterators[name], endAddress, getMemory, scanBufferingMode, epoch, headerSize, name, scanUncommitted, logger: logger);\n            else\n                iter = new FasterLogScanIterator(this, allocator, beginAddress, endAddress, getMemory, scanBufferingMode, epoch, headerSize, name, scanUncommitted, logger: logger);\n\n            if (name != null)\n            {\n                if (name.Length > 20)\n                    throw new FasterException(\"Max length of iterator name is 20 characters\");\n                if (PersistedIterators.ContainsKey(name))\n                    logger?.LogDebug(\"Iterator name exists, overwriting\");\n                PersistedIterators[name] = iter;\n            }\n\n            if (Interlocked.Increment(ref logRefCount) == 1)\n                throw new FasterException(\"Cannot scan disposed log instance\");\n            return iter;\n        }\n\n        /// <summary>\n        /// Random read record from log, at given address\n        /// </summary>\n        /// <param name=\"address\">Logical address to read from</param>\n        /// <param name=\"estimatedLength\">Estimated length of entry, if known</param>\n        /// <param name=\"token\">Cancellation token</param>\n        /// <returns></returns>\n        public async ValueTask<(byte[], int)> ReadAsync(long address, int estimatedLength = 0, CancellationToken token = default)\n        {\n            token.ThrowIfCancellationRequested();\n            epoch.Resume();\n            if (address >= CommittedUntilAddress || address < BeginAddress)\n            {\n                epoch.Suspend();\n                return default;","sourceCodeStart":1824,"sourceCodeEnd":1860,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/FasterLog/FasterLog.cs#L1824-L1860","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the log instance is not disposed before all Scan calls finish; keep logRefCount held via a live reference.","Fix shutdown ordering: cancel/await iterator consumer tasks before disposing the FasterLog.","If the instance was disposed intentionally, create a new FasterLog from the same settings instead of scanning the old one."],"exampleFix":"// before\nlog.Dispose();\nvar iter = log.Scan(0, long.MaxValue);\n// after\nvar iter = log.Scan(0, long.MaxValue);\n// consume...\niter.Dispose();\nlog.Dispose();","handlingStrategy":"try-catch","validationCode":"// track disposal in your wrapper\nif (Interlocked.Read(ref disposed) != 0) throw new ObjectDisposedException(nameof(FasterLog));","typeGuard":null,"tryCatchPattern":"try { var iter = log.Scan(0, long.MaxValue); } catch (FasterException ex) when (ex.Message.Contains(\"disposed log instance\")) { // recreate log or stop consuming\n}","preventionTips":["Bind FasterLog lifetime to the host (e.g. IHostedService.StopAsync disposes last).","Cancel iterator loops before disposal and await them.","Wrap FasterLog in a class that guards all access with a disposed flag."],"tags":["fasterlog","disposed","lifecycle","csharp"],"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"}