microsoft/FASTER · error · FasterException
Iterator address is less than log HeadAddress in…
Error message
Iterator address is less than log HeadAddress in memory-scan mode
What it means
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.
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
Example fix
// before
using var iter = fht.Log.Scan(fht.Log.BeginAddress, fht.Log.TailAddress, scanCallback, empty);
// after
var begin = Math.Max(fht.Log.BeginAddress, fht.Log.HeadAddress);
using var iter = fht.Log.Scan(begin, fht.Log.TailAddress, scanCallback, empty,
ScanBufferingMode.SinglePage); Defensive patterns
Strategy: validation
Validate before calling
// Before scanning, clamp beginAddress and use buffering if below head
long safeBegin = Math.Max(beginAddress, fht.Log.HeadAddress);
if (beginAddress < fht.Log.HeadAddress)
scanMode = ScanBufferingMode.SinglePage; // need a frame to read below head Try / catch
try
{
using var iter = fht.Log.Scan(begin, end, cb, ctx, ScanBufferingMode.SinglePage);
while (iter.GetNext(out var recordInfo)) { /* ... */ }
}
catch (FasterException ex) when (ex.Message.Contains("less than log HeadAddress"))
{
// restart scan from current head
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- Iterator address is less than log HeadAddress in…
- Cannot use scanUncommitted with read-only FasterLog
- Out of order message within session
- Unexpected status of SubscribeKV
- Cannot use BlittableParameterSerializer with non-blittable…
AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15).
Data as JSON: /api/errors/a13f33543eb2730a.
Report an issue: GitHub.
Appendix: source
Thrown at cs/src/core/Allocator/GenericScanIterator.cs:99
currentPage = currentOffset = currentFrame = -1;
while (true)
{
currentAddress = nextAddress;
if (currentAddress >= endAddress)
return false;
epoch?.Resume();
var headAddress = hlog.HeadAddress;
if (currentAddress < hlog.BeginAddress)
currentAddress = hlog.BeginAddress;
// If currentAddress < headAddress and we're not buffering, fail.
if (frameSize == 0 && currentAddress < headAddress)
{
epoch?.Suspend();
throw new FasterException("Iterator address is less than log HeadAddress in memory-scan mode");
}
currentPage = currentAddress >> hlog.LogPageSizeBits;
currentOffset = (currentAddress & hlog.PageSizeMask) / recordSize;
if (currentAddress < headAddress)
BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, endAddress);
// Check if record fits on page, if not skip to next page
if ((currentAddress & hlog.PageSizeMask) + recordSize > hlog.PageSize)
{
nextAddress = (1 + (currentAddress >> hlog.LogPageSizeBits)) << hlog.LogPageSizeBits;
epoch?.Suspend();
continue;
}
nextAddress = currentAddress + recordSize;
View on GitHub (pinned to 321d872eab)