Repository navigation
test(e2e): don't require an ignored SIGTERM on Windows in stdio shutdown-escalation - #3123
Conversation
…own-escalation
Windows has no POSIX signals: subprocess.kill('SIGTERM') terminates the child outright, so the fixture can never log that it ignored SIGTERM. Skip only that marker check on win32; the check that close() leaves no live child still runs everywhere. Refs modelcontextprotocol#3117.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
|
Read the diff against One thing the Windows branch no longer proves: that the child survived rung 1 (stdin EOF). If the fixture exited on EOF, That's a suggestion; the PR is correct as written for what it claims. |
|
Good catch, thanks. You're right that on Windows the test no longer proved the child survived stdin EOF. I checked it with a mutation: making the fixture exit on stdin Added it for all platforms rather than only inside the |
|
Read the updated diff: the lower bound outside the win32 branch is the right shape, and the mutation check (fixture exiting on stdin One thing I could not tell from the diff: is the 2s grace a constant exported from the transport, or a literal in |
|
It's a literal: both rungs in I'd rather not export it. The e2e test imports from the public |
|
Fair, keeping the 2s literal private makes sense, and a failing lower-bound assertion on a shortened grace period does get noticed. One thing I would check in the test: is the elapsed time measured with a monotonic clock ( |
|
Yes, it uses \performance.now()\ (monotonic), and the bound is 1900 ms. |
|
Good, performance.now() with the 1900 floor is the right pairing. One thing left that I could see in the diff: the bound is lower-only, so it proves the SIGKILL did not fire early but not that it fired at all within the grace window, and that part rests on the existing |
|
I'd leave the upper bound out. Unlike the floor, a ceiling is exactly what a loaded runner can break while the code is fine, and the cases it would catch are already covered: a Small note: the floor shows the child survived stdin EOF (rung 1), not anything about when SIGKILL fired. I'd like to keep this PR scoped to the Windows fix. Thanks for the careful reviews! |
|
Agreed on the ceiling: a failure message is the only thing it would add, and a loaded runner can trip it with correct code. And thanks for the note on the floor: it shows rung 1 survived stdin EOF, not when SIGKILL fired, so I will read it that way. One question on the Windows side: with no SIGTERM there, does the |
|
Plenty of slack. The repo's CI has no Windows runners (only
That's expected: on Windows |
|
Those numbers settle it for me: 2008-2024 ms for |
|
Sure. A Windows job isn't decided yet (it's an open question in #3117), but if one is added I'll rerun the timing there. |
On Windows,
transport:stdio:shutdown-escalationno longer requires the child to log that it ignored SIGTERM; it still requiresclose()to leave no live child. Case 3 of #3117.Motivation and Context
The scenario fails on Windows because it waits for the fixture to log
[stdio-server] sigterm ignored. Windows has no POSIX signals: per the Node docs,subprocess.kill('SIGTERM')kills the process "forcefully and abruptly (similar to 'SIGKILL')", so the fixture never gets the chance to ignore it.The transport is fine on Windows (the child does die). Only the test's premise is wrong there.
On
win32the test now skips only the marker check. The check thatclose()leaves no live child still runs on every platform, so on Windows the test still verifies something real. I also updated the comment above it, since "termination can only have come from SIGKILL" isn't true on Windows.How Has This Been Tested?
Windows 11 Pro 26100, Node 24.11.1, pnpm 10.26.1:
pnpm --filter @modelcontextprotocol/test-e2e test→ 2 failures (expected '[stdio-server] ready\n' to contain '[stdio-server] sigterm ignored').kill()calls inclose()commented out, the test fails on Windows atprocessAlive(pid), so the remaining assertion still catches a broken shutdown.tsc --noEmitand ESLint pass fortest/e2e.Breaking Changes
None. Test-only change, so no changeset.
Types of changes
Checklist
Additional context
I didn't skip the whole scenario because
verifies()has no per-platform skip (knownFailuresfilters by transport and spec version, not OS), and an earlyreturnwould pass without testing anything. If you'd rather have platform support inknownFailures, I can do that instead.Refs #3117
🤖 Generated with Claude Code