fix: read output streams straight from native memory - #47
Open
msallin wants to merge 1 commit into
Open
Conversation
OutputStream overrode only the byte-array reads, so span-shaped and asynchronous reads fell through to Stream.Read(Span), and CopyTo to Stream.CopyTo. Both stage the bytes through an array rented from the shared pool and return it uncleared, leaving the rendered document readable by whatever rents from that pool next, and copying every byte twice. The reads and the copy now go straight from the pointer. A 4 MiB span read drops from 0.767 ms to 0.274 ms, matching a plain UnmanagedMemoryStream. The remaining length is compared before it is narrowed to an int. Position may be seeked past the end, and narrowing a large negative long wraps back into range, which would otherwise read past the end of the buffer.
msallin
force-pushed
the
feat/output-stream-span-read
branch
from
September 6, 2026 19:51
0e95705 to
7ee660f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OutputStreamoverrode only the byte-array reads, so span-shaped and async reads fell through toStream.Read(Span), andCopyTotoStream.CopyTo. Both stage the bytes through an array rented fromArrayPool<byte>.Sharedand return it uncleared, leaving the rendered document readable by whatever rents from that pool next. They also copy every byte twice.Reads and the copy now go straight from the pointer. A 4 MiB span read: 0.767 ms to 0.274 ms, matching a plain
UnmanagedMemoryStream.ReadBytestill delegates to the base, which already reads the pointer directly.Worth a second look during review: the remaining length is compared before being narrowed to
int.Positioncan be seeked past the end, and narrowing a large negativelongwraps back into range.Tests: pool residue after a span read and after
CopyTo(both assert the same pooled array came back, so they cannot pass vacuously), reads past the end including the wrapping case, span and EOF boundaries, uneven chunked reads,ReadAsync/CopyToAsynccontent.63/63 pass, clean on net8/9/10.