Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ internal class JsonLinesReader(HttpResponseMessage message, IMemoryArenaSource a
internal class JsonLinesReader(HttpResponseMessage message) : IAsyncEnumerable<OperationResult>
#endif
{
#if FUSION
private const int MaxSingleSpanRecordLength = 16 * 1024;

#endif
private static readonly StreamPipeReaderOptions s_options = new(
pool: MemoryPool<byte>.Shared,
bufferSize: 4096,
Expand Down Expand Up @@ -98,9 +102,36 @@ public async IAsyncEnumerator<OperationResult> GetAsyncEnumerator(
#if FUSION
private static SourceResultDocument ParseDocument(IMemoryArena arena, ReadOnlySequence<byte> lineBuffer)
{
// Each record is one newline-delimited line. Its content bytes are filled once, directly into
// the arena's geometric segments using the same per-record mechanic as the SSE reader, then
// parsed in place via ParseFilled so no bytes are copied a second time.
var lineLength = (int)lineBuffer.Length;

// A record whose length fits within MaxSingleSpanRecordLength is filled once into a single
// exact-length arena chunk and parsed in place as one span. This skips the geometric ramp fill
// and the multi-segment reader it produces. The record length is known from newline framing, so
// this does not depend on a Content-Length header.
//
// The threshold is capped at 16 KB because the request-scoped arena is shared by all concurrent
// subgraph fetches and lives until the response is written. A large exact-length rent that does
// not fit the current page strands the whole remaining page tail for the rest of the request,
// while the geometric ramp's small leading chunks can fill those tails. Small records, which
// cover all realistic traffic, get the single-span win. Large records keep the tail-filling ramp.
if (lineLength > 0 && lineLength <= MaxSingleSpanRecordLength)
{
var buffer = arena.Rent(lineLength);
lineBuffer.CopyTo(buffer.Span);
Comment thread
Copilot marked this conversation as resolved.
Outdated

var segments = arena.RentSegmentTable(1);
segments[0] = buffer;

return SourceResultDocument.ParseFilled(
arena,
segments,
usedChunks: 1,
lastLength: lineLength);
}

// Fallback: larger records (and the degenerate empty line) are filled across the geometric data
// chunk schedule and parsed as a multi-segment sequence, using the same per-record mechanic as
// the SSE reader.
var chunks = arena.RentSegmentTable(64);
var chunkIndex = 0;
var chunkSize = SourceResultDocument.GetDataChunkSize(chunkIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private SourceResultDocument(MetaDb parsedData, MemorySegment[] segments, int us
// into the following chunks, and the read path walks the boundaries.
private const int DataOffsetBits = 17;
private const int DataChunkBits = 12;
private const int DataOffsetMask = (1 << DataOffsetBits) - 1;
internal const int DataOffsetMask = (1 << DataOffsetBits) - 1;
Comment thread
Copilot marked this conversation as resolved.
Outdated
internal const int DataMaxChunks = 1 << DataChunkBits;
private const int DataMaxChunkOrdinal = (int)ChunkSize.Size128K;

Expand Down
Loading