Skip to content

internal/jsonrpc2: allow EOF-side responses to drain#1067

Open
ychampion wants to merge 1 commit into
modelcontextprotocol:mainfrom
ychampion:fix-stdio-eof-drain
Open

internal/jsonrpc2: allow EOF-side responses to drain#1067
ychampion wants to merge 1 commit into
modelcontextprotocol:mainfrom
ychampion:fix-stdio-eof-drain

Conversation

@ychampion

Copy link
Copy Markdown
Contributor

Read-side EOF currently marks the whole connection as shutting down before handlers for already-read calls can write their responses. For stdio servers driven by one-shot pipes, that drops responses even though stdout is still writable.

This treats io.EOF as a read half-close for in-flight responses: EOF no longer cancels already accepted incoming requests, and response writes only reject explicit close, broken writer, or non-EOF read errors.

Fixes #1061

Validation:

  • red-before: go test ./internal/jsonrpc2 -run TestReadEOFAllowsInFlightResponseToDrain -count=1 failed with Wait returned error: server is closing: EOF
  • go test ./internal/jsonrpc2 -run TestReadEOFAllowsInFlightResponseToDrain -count=1
  • go test ./internal/jsonrpc2 -count=1
  • go test ./mcp -run 'Test(Stdio|Server|Client|InMemory)' -count=1\n- go test ./...\n- go vet ./...\n- git diff --check

@ychampion
ychampion force-pushed the fix-stdio-eof-drain branch 2 times, most recently from ad43d63 to d4fa0d6 Compare July 15, 2026 21:13
Treat read-side EOF as a half-close so already-dispatched requests can finish while the connection still rejects new work and propagates real transport failures.

Constraint: Preserve explicit close, write-error, and non-EOF read-error shutdown behavior.
Rejected: Drain after every read failure | only clean EOF proves the peer stopped sending without invalidating in-flight responses.
Confidence: high
Scope-risk: narrow
Directive: Keep EOF draining covered at the connection boundary rather than transport-specific callers.
Tested: Focused race regression 100x, internal/jsonrpc2 race suite 10x, go test -v -race ./..., go test ./..., go vet ./..., gofmt, and diff checks.
Not-tested: External conformance locally; the first rebased head passed both CI conformance suites before the test-only race fix.
@ychampion
ychampion force-pushed the fix-stdio-eof-drain branch from d4fa0d6 to 4b57174 Compare July 15, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stdio: responses to already-received requests are dropped when stdin EOF arrives first (one-shot pipes get no output)

1 participant