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

  1. Branch on IsMemoryLogRecord / IsDiskLogRecord before calling either As-ref method.
  2. Prefer the ISourceLogRecord interface accessors over casting to the concrete LogRecord/DiskLogRecord.
  3. 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

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


AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13). Data as JSON: /api/errors/9227b4e924de989c. Report an issue: GitHub.