{"record":{"id":"a13f33543eb2730a","repo":"microsoft/FASTER","slug":"iterator-address-is-less-than-log-headaddress-in-memory-scan-a13f33","errorCode":null,"errorMessage":"Iterator address is less than log HeadAddress in memory-scan mode","messagePattern":"Iterator address is less than log HeadAddress in memory-scan mode","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"error","filePath":"cs/src/core/Allocator/GenericScanIterator.cs","lineNumber":99,"sourceCode":"            currentPage = currentOffset = currentFrame = -1;\n\n            while (true)\n            {\n                currentAddress = nextAddress;\n                if (currentAddress >= endAddress)\n                    return false;\n\n                epoch?.Resume();\n                var headAddress = hlog.HeadAddress;\n\n                if (currentAddress < hlog.BeginAddress)\n                    currentAddress = hlog.BeginAddress;\n\n                // If currentAddress < headAddress and we're not buffering, fail.\n                if (frameSize == 0 && currentAddress < headAddress)\n                {\n                    epoch?.Suspend();\n                    throw new FasterException(\"Iterator address is less than log HeadAddress in memory-scan mode\");\n                }\n\n                currentPage = currentAddress >> hlog.LogPageSizeBits;\n                currentOffset = (currentAddress & hlog.PageSizeMask) / recordSize;\n\n                if (currentAddress < headAddress)\n                    BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, endAddress);\n\n                // Check if record fits on page, if not skip to next page\n                if ((currentAddress & hlog.PageSizeMask) + recordSize > hlog.PageSize)\n                {\n                    nextAddress = (1 + (currentAddress >> hlog.LogPageSizeBits)) << hlog.LogPageSizeBits;\n                    epoch?.Suspend();\n                    continue;\n                }\n\n                nextAddress = currentAddress + recordSize;\n","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/Allocator/GenericScanIterator.cs#L81-L117","documentation":"FASTER's scan iterator in memory-scan mode (frameSize == 0, i.e. no epoch protection frame) must keep up with the log's HeadAddress: records below the head have been deleted/truncated to disk. If iteration starts or resumes at an address below HeadAddress and no frame is buffering pages, the iterator cannot safely read records, so GetNext suspends the epoch and throws FasterException.","triggerScenarios":"Creating a scan iterator (hlog.Scan) at a beginAddress below the current log.HeadAddress without a frame (ScanBufferingMode no-buffering), or holding an iterator open long enough that truncation by ongoing checkpoints advances HeadAddress past the iterator's current address.","commonSituations":"Long-running scans on an active log where head truncation overtakes the iterator; scanning from log.BeginAddress on a log that has already been truncated; forgetting to pass ScanBufferingMode with a frame to Scan for reads below head.","solutions":["Pass a buffering mode with a frame to Scan (e.g. ScanBufferingMode.SinglePage or DoublePage) so pages below head can be buffered","Begin the scan at Math.Max(beginAddress, log.HeadAddress) instead of BeginAddress if reading only current records is acceptable","Periodically refresh/reset the iterator and track scanned range yourself if scans outlast truncation intervals","Use epoch protection (keep the epoch scheme active) and ensure the iterator is consumed before checkpoints truncate past its address"],"exampleFix":"// before\nusing var iter = fht.Log.Scan(fht.Log.BeginAddress, fht.Log.TailAddress, scanCallback, empty);\n// after\nvar begin = Math.Max(fht.Log.BeginAddress, fht.Log.HeadAddress);\nusing var iter = fht.Log.Scan(begin, fht.Log.TailAddress, scanCallback, empty,\n    ScanBufferingMode.SinglePage);","handlingStrategy":"validation","validationCode":"// Before scanning, clamp beginAddress and use buffering if below head\nlong safeBegin = Math.Max(beginAddress, fht.Log.HeadAddress);\nif (beginAddress < fht.Log.HeadAddress)\n    scanMode = ScanBufferingMode.SinglePage; // need a frame to read below head","typeGuard":null,"tryCatchPattern":"try\n{\n    using var iter = fht.Log.Scan(begin, end, cb, ctx, ScanBufferingMode.SinglePage);\n    while (iter.GetNext(out var recordInfo)) { /* ... */ }\n}\ncatch (FasterException ex) when (ex.Message.Contains(\"less than log HeadAddress\"))\n{\n    // restart scan from current head\n}","preventionTips":["Always start scans at Max(BeginAddress, HeadAddress) unless you pass a buffering frame","Consume iterators promptly; long-lived iterators get overtaken by truncation","Use ScanBufferingMode.SinglePage/DoublePage for reads below head","Check HeadAddress immediately before creating each scan iterator"],"tags":["faster","iterator","scan","log-truncation"],"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"}