You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[TST-E8] GatedStream's close leaves a pending read waiting, so net472 readiness rows test the release bound's expiry instead of the close #636
Lane: Runtime. Split out of #441, whose timing cost RT-E2 C removed; this is the realism gap that remains.
What
tests/OpenCode.Sdk.Tests/Support/GatedStream doesn't override Dispose, so closing it leaves a pending ReadChunkAsync waiting. A real pipe's pending read ends when its own end is closed.
Measured on 2026-10-26 with an overlapped NamedPipeServerStream, the type the Windows launcher reads through. The
reader's own end was disposed while a read was pending:
On net472, PipeLineReader's ReadLineAsync() takes no token, so only the close can end its read.
OpenCodeServerReadinessTests' recorded-exit rows hold a gated stdout pending through the release.
They pass because AdvanceEarlyEndAsync advances PipeOutputPump.ReleaseBound on the manual clock
(OpenCodeServerReadinessTests.cs:302-306 at d5045f87). That is the bound-expiry path, which production doesn't
take on a real pipe: there, the close ends the read.
So on net472 those rows test a different release path from the one the launcher runs.
There's no timing cost left (the rows take 0.11 to 0.70 s on net472), so this is test realism, not a 1.0 risk.
Proposed design
GatedStream: override Dispose(bool) to complete the release channel's writer. ReadChunkAsync waits with WaitToReadAsync(cancellationToken) and returns 0 when the channel is completed and empty. A token still cancels a
pending read, and a release queued before the close still hands out its chunk.
A new GatedStreamTests with two rows:
a pending read returns 0 after the close;
a release queued before the close, then the close, then a read that returns the chunk. This pins that the close
keeps queued releases.
Already checked: a read-only scan of all seven existing GatedStream users at d5045f87 (the readiness, owned
child, cancellation identity, caller stream content, pipeline stream, pipeline and response buffering classes) found
no test that relies on a closed GatedStream staying pending. Each still has to be run on every TFM.
Honest mutant label:
removing the Dispose override leaves the read pending forever, so that mutant can only be detected by a bounded
wait (the hang guard), not killed at an oracle;
pair it with an oracle-killable mutant: a closed read returns a byte instead of 0.
Prompt notes kept from the scan: use one --treenode-filter per class with --report-trx and a nonzero-count
check; and the pin probe must use the Memory<byte>ReadAsync overload (CA1835 is an error).
Activity