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
Thrown by BlittableScanIterator.GetNext when the iterator's next scan address is below the log's HeadAddress while operating in memory-scan mode (frameSize == 0), the records are not guaranteed buffered, and forceInMemory was not requested. FASTER refuses to scan because records older than the log head may have been deleted from disk, so a full scan would return truncated or wrong data. It fails fast rather than silently skipping missing records.
Solutions
- Ensure the iterator's BeginAddress is >= the log's current HeadAddress before scanning (refresh/advance the iterator's begin address).
- Configure the log with a non-zero memory/frame size so scan coverage is buffered, e.g. increase LogSettings.MemorySizeBits or use EpochProtectionScope/frame options for the iterator.
- Pass forceInMemory: true only if you can guarantee the range is still resident (otherwise you get silent data loss).
- Recreate the scan starting from hlog.BeginAddress (or the log's current BeginAddress) instead of a stale address.
- If a persisted iterator resume fails, snapshot/advance iterator tokens frequently so they never lag behind the truncated head.
Example fix
// before: resuming a stale iterator address var iter = log.Scan(staleIteratorAddress, log.TailAddress, scanBufferingMode); // after: clamp the begin address to what is still available var begin = log.BeginAddress; var from = staleIteratorAddress < begin ? begin : staleIteratorAddress; var iter = log.Scan(from, log.TailAddress, ScanBufferingMode.BufferPaged);
Defensive patterns
Strategy: validation
Validate before calling
// Before scanning, ensure the iterator start is still available
if (scanFromAddress < log.BeginAddress)
{
scanFromAddress = log.BeginAddress; // or re-create the iterator / fail the operation
} Type guard
static bool CanScanFrom(BlittableScanIterator<K,V> it, long headAddress, long frameSize, bool forceInMemory)
=> forceInMemory || frameSize > 0 || !(it.NextAddress < headAddress); Try / catch
try
{
while (iter.GetNext(out RecordInfo info, out var key, out var value)) { /* ... */ }
}
catch (FasterException ex) when (ex.Message.Contains("less than log HeadAddress"))
{
// restart scan from log.BeginAddress or re-snapshot the iterator
iter = log.Scan(log.BeginAddress, log.TailAddress);
} Prevention
- Persist/advance iterator tokens frequently so they never fall behind the truncated head.
- Configure adequate frame size or scan buffering (ScanBufferingMode.BufferPaged) for iterators that may lag.
- Clamp any stored begin address against log.BeginAddress before resuming a scan.
- Never pass forceInMemory: true unless you can prove the range is resident.
When it happens
Trigger: Iterating a FasterLog or scan iterator where currentAddress < headAddress, the log was created with a memory size / frameSize of 0 (no buffering), and the iterator was neither initialized at BeginAddress nor given enough tail-space coverage to keep the range in memory.
Common situations: Opening an iterator over a long-running log whose head has advanced past the stored iterator begin address; resuming a persisted iterator token after the log has flushed/truncated; scanning a log configured without in-memory buffering or an epoch protection frame.
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…
- Out of order message within session
- Unexpected status of SubscribeKV
- Cannot use BlittableParameterSerializer with non-blittable…
- The inner list is full!
AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15).
Data as JSON: /api/errors/ba8aa71de91d8ba9.
Report an issue: GitHub.
Appendix: source
Thrown at cs/src/core/Allocator/BlittableScanIterator.cs:102
recordInfo = default;
while (true)
{
currentAddress = nextAddress;
if (currentAddress >= endAddress)
return false;
epoch?.Resume();
var headAddress = hlog.HeadAddress;
if (currentAddress < hlog.BeginAddress && !forceInMemory)
currentAddress = hlog.BeginAddress;
// If currentAddress < headAddress and we're not buffering and not guaranteeing the records are in memory, fail.
if (frameSize == 0 && currentAddress < headAddress && !forceInMemory)
{
epoch?.Suspend();
throw new FasterException("Iterator address is less than log HeadAddress in memory-scan mode");
}
var currentPage = currentAddress >> hlog.LogPageSizeBits;
var offset = currentAddress & hlog.PageSizeMask;
if (currentAddress < headAddress && !forceInMemory)
BufferAndLoad(currentAddress, currentPage, currentPage % frameSize, headAddress, endAddress);
long physicalAddress = GetPhysicalAddress(currentAddress, headAddress, currentPage, offset);
var recordSize = hlog.GetRecordSize(physicalAddress).Item2;
// If record does not fit on page, skip to the next page.
if ((currentAddress & hlog.PageSizeMask) + recordSize > hlog.PageSize)
{
nextAddress = (1 + (currentAddress >> hlog.LogPageSizeBits)) << hlog.LogPageSizeBits;
epoch?.Suspend();
continue;
}View on GitHub (pinned to 321d872eab)