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

  1. Pass a buffering mode with a frame to Scan (e.g. ScanBufferingMode.SinglePage or DoublePage) so pages below head can be buffered
  2. Begin the scan at Math.Max(beginAddress, log.HeadAddress) instead of BeginAddress if reading only current records is acceptable
  3. Periodically refresh/reset the iterator and track scanned range yourself if scans outlast truncation intervals
  4. 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

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


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)