microsoft/garnet · error · InvalidOperationException
Stream does not support SetLength.
Error message
Stream does not support SetLength.
What it means
PinnedMemoryStream is a forward-only, fixed-capacity stream over a pre-allocated, pinned byte buffer used during Tsavorite object serialization (native pointers alias the buffer). SetLength is intentionally unsupported because the buffer is allocated once and pinned for the GC; resizing/truncating would break the pinned-pointer memory model. Seek is unsupported for the same reason.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs:127
catch (Exception ex)
{
return ValueTask.FromException<int>(ex);
}
}
/// <summary>Returns the byte at the current streamBuffer position and advances the position</summary>
/// <returns>The byte read (as an int)</returns>
public override unsafe int ReadByte()
{
byte b = default;
return streamBuffer.Read(new Span<byte>(ref b)) > 0 ? b : -1;
}
/// <summary>Seeking is not supported in this stream.</summary>
public override long Seek(long offset, SeekOrigin loc) => throw new InvalidOperationException("Stream does not support Seek.");
/// <summary>Seeking is not supported in this stream.</summary>
public override void SetLength(long value) => throw new InvalidOperationException("Stream does not support SetLength.");
/// <summary>Write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively
/// (e.g. to disk or network) as needed.</summary>
/// <param name="buffer">Buffer to write the bytes from.</param>
/// <param name="offset">Index in the buffer to start writing from.</param>
/// <param name="count">Desired number of bytes to write from the buffer.</param>
public override void Write(byte[] buffer, int offset, int count)
{
ValidateBufferArguments(buffer, offset, count);
streamBuffer.Write(new ReadOnlySpan<byte>(buffer, offset, count));
}
/// <summary>Write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively
/// (e.g. to disk or network) as needed.</summary>
public override void Write(ReadOnlySpan<byte> destinationSpan) => streamBuffer.Write(destinationSpan);
/// <summary>Asynchronously write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively
/// (e.g. to disk or network) as needed.</summary>View on GitHub (pinned to 951b0fc683)
Solutions
- Do not call SetLength on PinnedMemoryStream; size the buffer correctly up front and recreate the stream when you need a fresh one.
- Guard the call with a capability check: PinnedMemoryStream returns false for CanSeek, so test CanSeek before SetLength.
- If you need a resizable/truncatable buffer, copy into a MemoryStream instead of aliasing the pinned buffer.
- Audit generic serialization helpers for unconditional SetLength/Seek calls before pointing them at pinned streams.
Example fix
// before stream.SetLength(0); stream.Position = 0; // after (recreate instead of mutating) stream.Dispose(); stream = new PinnedMemoryStream(new byte[capacity]);
Defensive patterns
Strategy: validation
Validate before calling
// PinnedMemoryStream returns CanSeek = false; only resize streams that advertise seek/length
if (stream.CanSeek && stream.CanWrite)
stream.SetLength(desiredLength);
else
stream = NewFreshPinnedStream(desiredCapacity); Type guard
// Streams backed by fixed pinned memory are not length-mutable static bool IsLengthMutable(Stream s) => s.CanSeek && s.CanWrite && s is not PinnedMemoryStream;
Prevention
- Never assume SetLength/Seek/Position work on a Stream subclass; test CanSeek first.
- Treat pinned/native-backed streams as fixed-capacity and recreate rather than mutate.
- Document stream contracts in serializer helpers: which operations are required.
When it happens
Trigger: Any code path calls stream.SetLength(value) on a PinnedMemoryStream instance. Typically a generic serializer, a copy/reset helper that calls SetLength(0) to clear, or a stream-wrapper that defensively resizes its target.
Common situations: Running a third-party serializer or BinaryFormatter-style copy routine over a PinnedMemoryStream; a 'reset stream' helper that truncates instead of recreating; wrapping the stream in a helper that abstracts over Stream and unconditionally mutates length.
Related errors
- Stream does not support get_Length.
- Stream does not support get_Position.
- Stream does not support Seek.
- estimatedRecordSize ({estimatedTotalSize}) exceeds max alloc
- invalid metadata length: {hlri.cookie.Length} < 4
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/77534d761caa454a.
Report an issue: GitHub.