{"record":{"id":"77534d761caa454a","repo":"microsoft/garnet","slug":"stream-does-not-support-setlength","errorCode":null,"errorMessage":"Stream does not support SetLength.","messagePattern":"Stream does not support SetLength\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs","lineNumber":127,"sourceCode":"            catch (Exception ex)\n            {\n                return ValueTask.FromException<int>(ex);\n            }\n        }\n\n        /// <summary>Returns the byte at the current streamBuffer position and advances the position</summary>\n        /// <returns>The byte read (as an int)</returns>\n        public override unsafe int ReadByte()\n        {\n            byte b = default;\n            return streamBuffer.Read(new Span<byte>(ref b)) > 0 ? b : -1;\n        }\n\n        /// <summary>Seeking is not supported in this stream.</summary>\n        public override long Seek(long offset, SeekOrigin loc) => throw new InvalidOperationException(\"Stream does not support Seek.\");\n\n        /// <summary>Seeking is not supported in this stream.</summary>\n        public override void SetLength(long value) => throw new InvalidOperationException(\"Stream does not support SetLength.\");\n\n        /// <summary>Write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively \n        /// (e.g. to disk or network) as needed.</summary>\n        /// <param name=\"buffer\">Buffer to write the bytes from.</param>\n        /// <param name=\"offset\">Index in the buffer to start writing from.</param>\n        /// <param name=\"count\">Desired number of bytes to write from the buffer.</param>\n        public override void Write(byte[] buffer, int offset, int count)\n        {\n            ValidateBufferArguments(buffer, offset, count);\n            streamBuffer.Write(new ReadOnlySpan<byte>(buffer, offset, count));\n        }\n\n        /// <summary>Write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively \n        /// (e.g. to disk or network) as needed.</summary>\n        public override void Write(ReadOnlySpan<byte> destinationSpan) => streamBuffer.Write(destinationSpan);\n\n        /// <summary>Asynchronously write the buffer to the stream; the streamBuffer handles Flush, Reset, and Writing iteratively \n        /// (e.g. to disk or network) as needed.</summary>","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/Allocator/ObjectSerialization/PinnedMemoryStream.cs#L109-L145","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstream.SetLength(0);\nstream.Position = 0;\n\n// after (recreate instead of mutating)\nstream.Dispose();\nstream = new PinnedMemoryStream(new byte[capacity]);","handlingStrategy":"validation","validationCode":"// PinnedMemoryStream returns CanSeek = false; only resize streams that advertise seek/length\nif (stream.CanSeek && stream.CanWrite)\n    stream.SetLength(desiredLength);\nelse\n    stream = NewFreshPinnedStream(desiredCapacity);","typeGuard":"// Streams backed by fixed pinned memory are not length-mutable\nstatic bool IsLengthMutable(Stream s) => s.CanSeek && s.CanWrite && s is not PinnedMemoryStream;","tryCatchPattern":null,"preventionTips":["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."],"tags":["tsavorite","stream","serialization","notsupported","pinned-memory"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}