Repository navigation
Conversation
…max-total The test switched to fake timers and advanced the clock immediately after starting tools/call. Over SSE the request is a real HTTP POST that fake timers do not wait for, so when it was slow to land the 500ms per-request timeout fired before any progress was sent, and the test failed without ever exercising maxTotalTimeout. Resolve a promise when the tool handler starts and await it before advancing. Awaiting a promise does not move the fake clock, unlike vi.waitFor, which advances fake timers while it polls. On macOS, -t "timeout:basic|timeout:max-total" failed 5 of 20 SSE variants before this change and 0 of 40 after. Delaying only the tools/call POST by 50ms of real time fails both SSE variants on every run without the change and passes all 12 variants with it. Fixes modelcontextprotocol#3045
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
|
Read 61396ea (not run). The reasoning holds: the client-side timers are armed while the clock is frozen, so awaiting One failure mode: Question: |
Fixes #3045.
Before:
protocol:timeout:max-totalswitched to fake timers, startedtools/call, and advanced the clock straight away. Over SSE the request is a real HTTP POST that fake timers don't wait for, so when it landed late the 500ms per-request timeout fired before any progress was sent. The SSE variants then failed intermittently withexpected 0 to be greater than or equal to 3, and when they did, the test never exercisedmaxTotalTimeout.After: the tool handler resolves a promise as soon as it starts, and the test awaits it before advancing the clock. Awaiting a promise doesn't move the fake clock, unlike
vi.waitFor, which advances fake timers while it polls. Test-only change, 9 lines, no changeset.Motivation and Context
On a clean clone on macOS,
pnpm test:allintermittently fails on this test, while CI onmainis green. Details and the original investigation are in #3045.One correction to that issue: it says the failure is followed by an unhandled
SdkError: Request timed out.max-totalattachescall.catch(() => {}), so that rejection isn't from this test. It comes fromprotocol:timeout:reset-on-progress(data: { timeout: 200 }), which leaves 1–2 unhandled rejections on every run on macOS, with or without this change. That's a separate problem and I've left it out of this PR; happy to open an issue for it.How Has This Been Tested?
On macOS (Darwin 27), Node 24.16.0, from
main@39ae8e61:pnpm vitest run scenarios/protocol.test.ts -t "timeout:basic|timeout:max-total"intest/e2e, repeated. Before: 5 of 20 SSE variants failed (10 runs). After: 0 of 40 (20 runs).tools/callPOST by 50ms of real time (via asetTimeoutcaptured beforevi.useFakeTimers()). Without this change both SSE variants failed on 3 of 3 runs; with it all 12 variants passed on 3 of 3 runs. The patch isn't part of this PR.pnpm lint:allandpnpm --filter @modelcontextprotocol/test-e2e typecheckpass. Inpnpm test:allevery test passes; vitest still exits 1 because of thereset-on-progressunhandled rejections described above.