test(mcp): widen the EOF settle and state the pin's two bounds (TASK-3298)

Codex round 2 on 5889ff62 (P2): the EOF signal fires inside Read, before
the server handles EOF, so the release after it is a timed settle. A
cancel-at-EOF server delayed past it would pass. Nothing observable
separates "EOF handled" from "server never cancels", so the ordering
cannot be made exact from outside mcp-go. The settle goes from 200ms to
500ms, and the comment now states the bound: the error runs one way
only, and a server that keeps in-flight calls alive can never be failed
by it.

The same round's table found that a server running tool calls on the read
loop fails the entry gate instead of exercising EOF. The comment now names
that too: it means the pin's premise changed, not that EOF cancelled.

Controls under -race, same tree: mcp-go 1.1.0 PASS 3/3; 1.1.1 FAIL 3/3;
the cancel-at-EOF mutant with read-loop serving FAIL 3/3.

Claude-Session: https://claude.ai/code/session_016PXRzXhokr61CLbbu3Txj9
This commit is contained in:
xarmian
2026-09-29 12:40:17 +00:00
parent 5889ff6298
commit 6e1d268578
+11 -1
View File
@@ -34,11 +34,21 @@ import (
// resourceFallback when the server serves it on the read loop itself (as
// v1.1.0 does) and therefore cannot read EOF until it returns; in that shape
// EOF cannot cancel it, and the tool calls are still held across the EOF.
//
// Two bounds, stated rather than implied. (1) The signal fires inside Read,
// before the server has handled the EOF, and a server that never cancels
// emits nothing to wait for, so the release after it is a timed settle: a
// cancel-at-EOF server whose EOF handling is delayed past eofSettle would
// pass. That error runs one way only; a server that keeps in-flight calls
// alive can never be failed by it. (2) A server that runs tool calls on the
// read loop itself lets only one call in before stdin closes, so the entry
// gate fails with "only 1 of 4 calls reached"; that means this pin's premise
// no longer holds, not that EOF cancelled anything.
const (
// eofSettle is how long a release waits after EOF was observed, so a
// cancel-at-EOF server has cancelled before the release can win the race.
eofSettle = 200 * time.Millisecond
eofSettle = 500 * time.Millisecond
// resourceFallback releases a resource read that blocks the read loop.
resourceFallback = time.Second
)