Title: Deflake the session-level timeout test with trio's virtual clock by maxisbey · Pull Request #2788 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Deflake the session-level timeout test with trio's virtual clock by maxisbey · Pull Request #2788 · modelcontextprotocol/python-sdk
X Title: Deflake the session-level timeout test with trio's virtual clock by maxisbey · Pull Request #2788 · modelcontextprotocol/python-sdk
Description: test_session_level_timeout_applies_to_every_request flaked three times in its first week. This switches it to trio's virtual clock so it is deterministic by construction, with no real waiting. Motivation and Context The test sets Client(server, read_timeout_seconds=0.05). A session-level read timeout applies to every request that doesn't set its own — including the InitializeRequest sent inside Client.__aenter__ — so the in-process handshake had to beat 50ms of real clock before the test body even ran. Under CI load it sometimes didn't, and the test died in __aenter__ with Timed out while waiting for response to InitializeRequest: 3.12, locked, ubuntu-latest (May 30) 3.12, locked, windows-latest (Jun 4) 3.12, locked, windows-latest (Jun 5) Looping the in-memory handshake on a CPU-saturated windows-latest runner (3.12, locked) puts its tail at p99 ≈ 70ms and max ≈ 190ms — 4.3% of handshakes exceeded 50ms; even idle, the max was 26ms. So the existing value was a standing bet against scheduler stalls, and any wider real-clock margin pays for itself in wall-clock time on every run. The file's other tests are deterministic because the timed request can never be answered (the handler blocks forever), so an effectively-zero timeout always wins. The session-default test can't use that trick: the same knob governs the handshake, which must succeed first. Running this one test on trio's MockClock(autojump_threshold=0) removes the bet instead of widening it. Virtual time only advances when every task is blocked: during the handshake some task is always runnable, so the handshake cannot time out no matter how slow the runner, and once the tool call blocks on the never-answered request the run goes idle and the clock jumps straight to the deadline. The timeout value and snapshot stay as they were; the file now runs in milliseconds. trio is already a dev dependency, and the per-test anyio_backend parametrization keeps the rest of the suite on asyncio. How Has This Been Tested? 500 iterations of the test shape under the MockClock via the pytest plugin path: identical outcome every time, ~1.1s of real time for 25s of virtual timeouts. 30 consecutive runs of the file, all green; full tests/interaction/ suite passes (529 tests, ~5s). Sanity-checked the failure mode is unchanged: a tiny real-clock timeout racing the handshake is not a viable alternative — on Windows 3.12, time.monotonic has 15.625ms granularity, so fail_after(1e-6) actually lets the handshake win most races (and 3.13+ switched to QPC, flipping the behavior). Virtual time sidesteps clock granularity entirely. Breaking Changes None — test-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 All three flakes hit the 3.12/locked matrix cell. For the two Windows hits there's a plausible amplifier: on 3.12, asyncio's clock is GetTickCount64-based, so a 50ms deadline computed from a floor-quantized clock can fire up to ~15.6ms early in real time. The CI tracebacks above are also a live reproduction of #2114: the MCPError from the failed initialize surfaces from Client.__aenter__ double-wrapped in ExceptionGroups. AI Disclaimer
Open Graph Description: test_session_level_timeout_applies_to_every_request flaked three times in its first week. This switches it to trio's virtual clock so it is deterministic by construction, with no real waiting. ...
X Description: test_session_level_timeout_applies_to_every_request flaked three times in its first week. This switches it to trio's virtual clock so it is deterministic by construction, with no real waiti...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2788
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:811f8a55-1849-2b4b-d185-d1608b2019cc |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | D102:195D56:6AD109:8A0CC7:6A5ACD2A |
| html-safe-nonce | 576d275ee6538744aaa9efeadf3fded1dff8e3202db23662f0a365d657d08736 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEMTAyOjE5NUQ1Njo2QUQxMDk6OEEwQ0M3OjZBNUFDRDJBIiwidmlzaXRvcl9pZCI6IjgxNDI2OTAyNTE4NjM0MTIwMTAiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 29c1173e5da3e020aa766babb8ef5c28a649f8cad847d74f7861c272415d8933 |
| hovercard-subject-tag | pull_request:3812673797 |
| 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/2788/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 | test_session_level_timeout_applies_to_every_request flaked three times in its first week. This switches it to trio's virtual clock so it is deterministic by construction, with no real waiting. ... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 887a0902ea4987c46dd44cbc73dfadecb93a29560d7519a576fcecd81716148c |
| 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 |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 2606513ce6b72f8b775db6a60283f9cf7d4d70a5 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width