fix(worker): session-owned stream disposal unblocks and observes the net48 pipe read at teardown
This commit is contained in:
@@ -22,6 +22,14 @@ public sealed class WorkerFrameReader
|
||||
|
||||
// Reused across frames rather than allocated per read (GWC-30). Safe because ReadAsync is
|
||||
// single-consumer by construction; the prefix is fully overwritten by every read.
|
||||
//
|
||||
// The single-consumer invariant has to survive teardown as well as steady state, because a read
|
||||
// abandoned by WorkerPipeSession's message loop still owns this buffer (and its rented payload
|
||||
// buffer) until it faults. Nothing may call ReadAsync again after that point: a second read
|
||||
// would race the abandoned one for the prefix, and could hand a pooled payload buffer back to
|
||||
// ArrayPool twice. The loop guarantees it structurally — it only ever issues a read after
|
||||
// awaiting the previous one, and it never re-enters after unwinding — and teardown only awaits
|
||||
// the abandoned read, never reissues it (WorkerPipeSession.ObserveAbandonedPipeReadAsync).
|
||||
private readonly byte[] _lengthPrefix = new byte[sizeof(uint)];
|
||||
|
||||
/// <summary>Initializes the reader with a stream and protocol options.</summary>
|
||||
@@ -106,6 +114,12 @@ public sealed class WorkerFrameReader
|
||||
int offset = 0;
|
||||
while (offset < count)
|
||||
{
|
||||
// The token is forwarded but is NOT a bound on a pipe read: on .NET Framework 4.8
|
||||
// NamedPipeClientStream.ReadAsync accepts a CancellationToken and never wires it to the
|
||||
// overlapped I/O, so a read waiting on gateway bytes ignores cancellation entirely. Only
|
||||
// closing the handle ends it (WorkerPipeSession disposes the transport at teardown for
|
||||
// exactly this reason). It is still passed because non-pipe streams — the
|
||||
// MemoryStream-backed unit tests, and any future transport — do honor it.
|
||||
int bytesRead = await _stream
|
||||
.ReadAsync(buffer, offset, count - offset, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user