microsoft/garnet · error · InvalidOperationException
Cannot cast a DiskLogRecord to a memory LogRecord.
Error message
Cannot cast a DiskLogRecord to a memory LogRecord.
What it means
The disk-backed SpanByteScanIterator record is a DiskLogRecord wrapper over pinned transient (on-device-read) memory with a different layout from an in-memory LogRecord. AsMemoryLogRecordRef throws InvalidOperationException to prevent unsafe reinterpretation of one as the other. IsMemoryLogRecord is false for these records.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/SpanByteScanIterator.cs:406
}
/// <inheritdoc/>
public SpanByteAndMemory ValueSpanByteAndMemory => diskLogRecord.ValueSpanByteAndMemory;
/// <inheritdoc/>
public long ETag => diskLogRecord.ETag;
/// <inheritdoc/>
public long Expiration => diskLogRecord.Expiration;
/// <inheritdoc/>
public void ClearValueIfHeap() { } // Not relevant for "iterator as logrecord"
/// <inheritdoc/>
public bool IsMemoryLogRecord => false;
/// <inheritdoc/>
public unsafe ref LogRecord AsMemoryLogRecordRef() => throw new InvalidOperationException("Cannot cast a DiskLogRecord to a memory LogRecord.");
/// <inheritdoc/>
public bool IsDiskLogRecord => true;
/// <inheritdoc/>
public unsafe ref DiskLogRecord AsDiskLogRecordRef() => ref Unsafe.AsRef(in diskLogRecord);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RecordFieldInfo GetRecordFieldInfo() => diskLogRecord.GetRecordFieldInfo();
/// <inheritdoc/>
public int AllocatedSize => diskLogRecord.AllocatedSize;
/// <inheritdoc/>
public int ActualSize => diskLogRecord.ActualSize;
/// <inheritdoc/>View on GitHub (pinned to 951b0fc683)
Solutions
- Branch on IsMemoryLogRecord / IsDiskLogRecord before calling either As-ref method.
- Prefer the ISourceLogRecord interface accessors over casting to the concrete LogRecord/DiskLogRecord.
- Where you must have a memory record, obtain it from a memory scan iterator rather than the disk iterator.
Example fix
// before
ref LogRecord r = ref src.AsMemoryLogRecordRef();
// after
if (src.IsDiskLogRecord)
{
ref DiskLogRecord d = ref src.AsDiskLogRecordRef();
// ... disk-specific access
}
else
{
ref LogRecord m = ref src.AsMemoryLogRecordRef();
// ... memory-specific access
} Defensive patterns
Strategy: type-guard
Type guard
static bool IsMemoryRecord<T>(in T src) where T : ISourceLogRecord => src.IsMemoryLogRecord; static bool IsDiskRecord<T>(in T src) where T : ISourceLogRecord => src.IsDiskLogRecord;
Prevention
- Always check IsMemoryLogRecord/IsDiskLogRecord before calling either As-ref method.
- Prefer ISourceLogRecord interface accessors over casting to concrete record types.
- Test generic record helpers against both memory and disk scan iterators.
When it happens
Trigger: Calling AsMemoryLogRecordRef() on a record obtained from a disk scan iterator (where IsMemoryLogRecord is false and IsDiskLogRecord is true). Generic code over ISourceLogRecord that assumes every iterator record is a memory record.
Common situations: A generic helper written and tested against the in-memory scan path, later invoked on the disk-scan path; code that unconditionally calls AsMemoryLogRecordRef to access memory-only fields like PhysicalAddress.
Related errors
- Page read from storage failed, skipping page. Inner exceptio
- Iterator address is less than log HeadAddress in memory-scan
- TsavoriteLogAllocator Scan methods should not be used
- Specified transformer for RedisOptions property does not mat
- bftree_scan_with_count returned a null handle.
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/9227b4e924de989c.
Report an issue: GitHub.