microsoft/garnet · error · InvalidOperationException

FlushAndReset is not supported for DiskStreamReadBuffer

Error message

FlushAndReset is not supported for DiskStreamReadBuffer

What it means

DiskStreamReadBuffer is a read-only buffer used to surface object-log bytes from a disk stream during reads. Its FlushAndReset implementation deliberately throws InvalidOperationException because a read buffer holds no writable/flushable state. Calling FlushAndReset on it is a programming error — the caller invoked a write-side operation on a read-side object.

Source

Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/ObjectLogReader.cs:69

        /// </summary>
        /// <param name="filePosition">The initial file position to read</param>
        /// <param name="totalLength">The cumulative length of all object-log entries for the span of records to be read. We read ahead for all record
        ///     in the ReadAsync call.</param>
        internal void OnBeginReadRecords(ObjectLogFilePositionInfo filePosition, ulong totalLength)
        {
            inDeserialize = false;
            deserializedLength = 0UL;
            readBuffers.OnBeginReadRecords(filePosition, totalLength);
        }

        /// <summary>
        /// Called when one or more records with Objects have been read and via ReadAsync, e.g. being processed by AsyncReadPageWithObjectsCallback,
        /// and we have completed reading and deserializing those objects.
        /// </summary>
        internal void OnEndReadRecords() => readBuffers.OnEndReadRecords();

        /// <inheritdoc/>
        public void FlushAndReset(CancellationToken cancellationToken = default) => throw new InvalidOperationException("FlushAndReset is not supported for DiskStreamReadBuffer");

        /// <inheritdoc/>
        public void Write(ReadOnlySpan<byte> data, CancellationToken cancellationToken = default) => throw new InvalidOperationException("Write is not supported for DiskStreamReadBuffer");

        /// <summary>
        /// Get the object log entries for Overflow Keys and Values and Object Values for the input <paramref name="logRecord"/>. We do not create the log record here;
        /// that was already done by the caller from a single-record disk IO or from Recovery.
        /// <list type="bullet">
        /// <item>If there is an Overflow key, read it and if we have a <paramref name="requestedKey"/> compare it and return false if it does not match.
        ///     Otherwise, store the Key Overflow in the transient <see cref="ObjectIdMap"/> in <paramref name="logRecord"/>.
        ///     If we don't have <paramref name="requestedKey"/>, this is either ReadAtAddress (which is an implicit match) or Scan or Restore.</item>
        /// <item>If we have an Overflow or Object value, read and store it in the transient <see cref="ObjectIdMap"/> in <paramref name="logRecord"/>.</item>
        /// </list>
        /// </summary>
        /// <param name="logRecord">The initial record read from disk from Pending IO, so it is of size <see cref="IStreamBuffer.DefaultInitialIORecordSize"/> or less.</param>
        /// <param name="requestedKey">The requested key, if not ReadAtAddress; we will compare to see if it matches the record.</param>
        /// <param name="segmentSizeBits">Number of bits in segment size</param>
        /// <returns>False if requestedKey is set and we read an Overflow key and it did not match; otherwise true</returns>

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Before calling FlushAndReset, check the buffer type (e.g. 'is DiskStreamReadBuffer' / a CanWrite flag) and skip the call for read buffers.
  2. Ensure the correct buffer kind is passed to the call site — read buffers for read paths, write buffers for write paths.
  3. Restructure so read and write buffer interfaces separate the FlushAndReset capability.

Example fix

// before
buffer.FlushAndReset(cancellationToken);
// after
if (buffer is not DiskStreamReadBuffer)
    buffer.FlushAndReset(cancellationToken);
Defensive patterns

Strategy: type-guard

Validate before calling

if (buffer is DiskStreamReadBuffer) throw new InvalidOperationException("FlushAndReset not valid for read buffers");
buffer.FlushAndReset(cancellationToken);

Type guard

static bool CanFlushAndReset(IStreamBuffer b) => b is not DiskStreamReadBuffer;

Prevention

When it happens

Trigger: Code that holds an IStreamBuffer reference and unconditionally calls FlushAndReset, but the instance is actually a DiskStreamReadBuffer (returned for read paths such as ReadRecordObjects / disk-based object reads).

Common situations: Generic stream-buffer handling code that treats read and write buffers uniformly; a refactor that routed a read buffer into a path expecting a write buffer; misrouted serialization/deserialization buffer.

Related errors


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