Skip to content

Reset a CycleBuffer segment's start trim when it is recycled (#3302) - #3303

Merged
mgravell merged 2 commits into
mainfrom
marc/3302-cyclebuffer-trim
Oct 27, 2026
Merged

mgravell merged 2 commits into
mainfrom
marc/3302-cyclebuffer-trim

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Fixes #3302.

CycleBuffer.Segment.Recycle() returns segments to a process-wide _spare slot without resetting StartTrimCount, and Create/Init don't reset it either, so the next buffer to take that segment (any connection, any multiplexer) inherits a trim that never happened. Segments reach the slot still trimmed via Release() and via AppendOrRecycle's search-exhausted path (after ResetAndGetNext, which restores Memory but not the trim) - the latter in steady state for multi-segment buffers, not just on teardown.

The stale value bites in Commit -> CopyDueToDiscardDuringWrite: when the buffer is fully drained while a write lease is open (the outbound copy-out loop discarding while the writer holds GetMemory), Untrim zeroes the trim and the copy reads from leasedStart, which still includes the stale offset - so the wrong bytes go out.

  • Cherry-picks de30137 from the v4 line: reset StartTrimCount in Recycle, plus its test.
  • Adds DiscardDuringWriteKeepsLeasedBytesAfterARecycledTrimmedSegment, which checks the bytes rather than the field: without the fix the second buffer commits 789 (the previous buffer's payload) instead of XYZ.

RESPite.Tests pass on net10.0 (1719/1719).

Absorbed from PR #3251's branch (0ce150b), where its read/parse stress test
found it. Segment.Recycle returns the segment to a process-wide spare slot,
from which Segment.Create hands it to any buffer as pristine - but it kept
StartTrimCount, and some paths recycle without Untrim first (Release, and
AppendOrRecycle's search-exhausted path). The next buffer's 'new' segment
then carried a trim that never happened: Debug.Assert(leasedStart == 0) in
Debug, and in Release a later Untrim expanding backwards by it, corrupting
the chain's running indexes.

ARecycledTrimmedSegmentStartsCleanInTheNextBuffer: trim, Release, then a new
buffer's first segment - StartTrimCount 4 on the old code, 0 now.
)

A discard that drains the buffer while a write lease is open - the outbound
copy-out loop racing the writer - copied from leasedStart, which included the
stale trim; the 'new' buffer emitted bytes from the previous owner's payload
('789' instead of 'XYZ').
@mgravell
mgravell merged commit ee47e53 into main Oct 27, 2026
5 checks passed
@mgravell
mgravell deleted the marc/3302-cyclebuffer-trim branch October 27, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant