Title: Flush the stdio subprocess's coverage data before the clean-exit line by maxisbey · Pull Request #2840 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Flush the stdio subprocess's coverage data before the clean-exit line by maxisbey · Pull Request #2840 · modelcontextprotocol/python-sdk
X Title: Flush the stdio subprocess's coverage data before the clean-exit line by maxisbey · Pull Request #2840 · modelcontextprotocol/python-sdk
Description: Makes the stdio interaction test's subprocess flush its coverage data before printing the clean-exit line, so the transport's terminate escalation can no longer silently destroy the data file. Motivation and Context checks / test (3.10, locked, windows-latest) failed on #2838 with every test passing but the 100% gate tripping on tests\interaction\transports\_stdio_server.py at 52.17% — exactly the subprocess-only lines (failing job). Root cause: the child only persisted its coverage data in coverage's atexit hook, which runs after the stdio-echo: clean exit print the test synchronizes on. The failing run's test took 12.40s ≈ 2.4s session + exactly the 10.0s PROCESS_TERMINATION_TIMEOUT the test monkeypatches — the child printed the line (test green), then didn't finish interpreter teardown + the sqlite save within the grace, and the Windows escalation (TerminateJobObject) killed it mid-save. A torn save leaves a valid-but-empty data file that coverage combine merges without a warning, which is why the failing run still combined the same 6 data files as passing runs. This is the same race #2773 narrowed by raising the grace from 2s to 10s; the wider grace made it rarer, not impossible. Extracting this test's wall time from ~400 recent Windows CI job logs shows a smooth heavy tail — p50 3.3s, p99 ~7s, then 8.8s and 9.0s (passed, barely under the grace) and 12.4s (killed) — so any finite grace loses occasionally as long as the data write happens after the observable sync point. The fix inverts the ordering instead: main() now stops and saves coverage before printing the clean-exit line. That makes the test's existing stderr assertion the durability barrier — once the parent has seen the line, the data is on disk and a late kill is harmless. A kill landing before the print now fails the stderr assertion loudly instead of tripping the coverage gate one step later. Details that fall out of the ordering: Nothing measured can execute after the flush (it would be unrecordable by construction), so the trailing cov.save() and print(...) lines carry # pragma: lax no cover. cov.stop() is load-bearing twice: it ends tracing, and it leaves nothing for coverage's atexit re-save to flush — so a kill during interpreter teardown can't corrupt the already-written file (coverage opens it with sqlite journaling off; a torn rewrite would not roll back). The if cov is not None branch is # pragma: no branch: under coverage the instance always exists, and without coverage nothing is measured anyway. The test's monkeypatch comment is updated to describe the new invariant; the 10s grace stays (it now only bounds how long a pre-print straggler gets before a loud failure). How Has This Been Tested? Reproduced the failure mode deterministically: with a 3s post-print stall injected into the child and a 1s grace, the pre-fix code passes the test while the child's data file goes missing (the CI signature on demand); the fixed code under the identical kill retains the complete data file. coverage run -m pytest tests/interaction/transports/test_stdio.py → both tests pass, _stdio_server.py reports 100.00% (statements + branches) from the combined parent+child data. Full ./scripts/test: 100.00% total, strict-no-cover clean. ~2,500 instrumented spawn/close cycles of this exact flow on windows-latest runners (and ~2,200 on Linux) while investigating: the child's post-print phase is interpreter teardown plus the coverage sqlite save, and the save is the phase that runner load stretches — which is what made the atexit-ordering the wrong side of the sync point. Breaking Changes None — test-support change only. Types of changes Bug fix (non-breaking change which fixes an issue) Checklist I have read the MCP Documentation My code follows the repository's style guidelines New and existing tests pass locally I have added appropriate error handling I have added or updated documentation as needed Additional context The import coverage at module top makes this test-support file depend on the dev environment even for the docstring's standalone python -m invocation; that's already true of the repo's dev venv everywhere this module is used, so it keeps imports-at-top rather than adding a guarded import plus more pragma machinery. AI Disclaimer
Open Graph Description: Makes the stdio interaction test's subprocess flush its coverage data before printing the clean-exit line, so the transport's terminate escalation can no longer silently destroy the data fi...
X Description: Makes the stdio interaction test's subprocess flush its coverage data before printing the clean-exit line, so the transport's terminate escalation can no longer silently destroy the...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2840
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:11365a99-d550-71c8-0534-2845aab01a8f |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 99EC:2E6F7D:215692E:2CBEF25:6A59A48B |
| html-safe-nonce | 57dd98953c1feba46960ba58d923332e57e32f7fe7a9829b0504d727663ddb09 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5OUVDOjJFNkY3RDoyMTU2OTJFOjJDQkVGMjU6NkE1OUE0OEIiLCJ2aXNpdG9yX2lkIjoiODEzOTkyNTE5NDg2Nzg0NDIzNiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 0a82afbc016300ea4493960a651ad952c24307148bb1e6a068685afe94119c9e |
| hovercard-subject-tag | pull_request:3850670396 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/2840/files |
| twitter:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| og:image:alt | Makes the stdio interaction test's subprocess flush its coverage data before printing the clean-exit line, so the transport's terminate escalation can no longer silently destroy the data fi... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | a540949572872b935b393b36db38922db390ae71c859537d741b8f3eb7e545b5 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 0b971869dd75eec26500a9de7e85b8137c6bf0f6 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width