Title: fix: eliminate test port allocation race by running uvicorn in-thread by maxisbey · Pull Request #2263 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: fix: eliminate test port allocation race by running uvicorn in-thread by maxisbey · Pull Request #2263 · modelcontextprotocol/python-sdk
X Title: fix: eliminate test port allocation race by running uvicorn in-thread by maxisbey · Pull Request #2263 · modelcontextprotocol/python-sdk
Description: Summary Replaces the subprocess + port-race fixture pattern in tests/shared/test_streamable_http.py with an in-thread uvicorn helper that pre-binds the listening socket, eliminating the TOCTOU race that caused intermittent CI failures under pytest-xdist. The Problem @pytest.fixture def event_server_port() -> int: with socket.socket() as s: s.bind(("127.0.0.1", 0)) return s.getsockname()[1] # ← Port released! Race window opens @pytest.fixture def event_server(event_server_port, event_store): proc = multiprocessing.Process(target=run_server, kwargs={"port": event_server_port, ...}) proc.start() wait_for_server(event_server_port) # ← May see WRONG server ... Between getsockname() returning and the subprocess rebinding, another pytest-xdist worker can claim the port — causing httpx.ConnectError: All connection attempts failed (job 63508538456) on the SSE auto-reconnect tests. PR #1528 attempted worker-specific port ranges but was closed as only reducing (not eliminating) collision probability. The Fix tests/test_helpers.py now provides run_uvicorn_in_thread(app) which: Binds a listening socket with port=0 and calls listen() before starting the server thread Passes that socket to uvicorn via server.run(sockets=[sock]) Yields the URL immediately — the port is known before the server thread even runs, and the kernel's listen queue buffers any connections that arrive during uvicorn startup No polling, no race window. The socket is held atomically from bind() until sock.close() at fixture teardown. Scope Migrated the four test_streamable_http.py fixtures (basic_server, event_server, json_response_server, context_aware_server) that share create_app(). These include the SSE auto-reconnect tests (test_server_close_sse_stream_via_context, test_streamable_http_client_auto_reconnects, test_streamable_http_multiple_reconnections, test_standalone_get_stream_reconnection, test_streamable_http_client_respects_retry_interval) which genuinely need real TCP to exercise connection lifecycle. Other files with the same pattern (test_sse.py, test_ws.py, test_http_unicode.py, test_integration.py, security tests) are deliberately left alone here — they don't need real TCP and are being converted to in-memory/ASGITransport in sibling PRs. wait_for_server() is kept for them. Coverage Running the server in-process means coverage now tracks transport code that was previously subprocess-invisible. Adjusted # pragma: no cover → # pragma: lax no cover on a handful of branches in streamable_http.py and transport_security.py that are now partially covered depending on which tests exercise them. AI Disclaimer
Open Graph Description: Summary Replaces the subprocess + port-race fixture pattern in tests/shared/test_streamable_http.py with an in-thread uvicorn helper that pre-binds the listening socket, eliminating the TOCTOU race...
X Description: Summary Replaces the subprocess + port-race fixture pattern in tests/shared/test_streamable_http.py with an in-thread uvicorn helper that pre-binds the listening socket, eliminating the TOCTOU race...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2263
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:daa166ea-74e1-84e6-c728-a02c3d56a68d |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 867A:264AA9:38B1B9:4B61AE:6A5B8F63 |
| html-safe-nonce | 1628e74d35569d46d0dfd33d9b542b111fe049f2a92934600d9aae54b2f11ef5 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NjdBOjI2NEFBOTozOEIxQjk6NEI2MUFFOjZBNUI4RjYzIiwidmlzaXRvcl9pZCI6IjgzMzQzNjUwOTQ1NzE2NDI3MjMiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 4a84f76fc13320923a8cfc2909e19a9c63a7bfb78772a2fb841180c202748f28 |
| hovercard-subject-tag | pull_request:3379017336 |
| 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/2263/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 | Summary Replaces the subprocess + port-race fixture pattern in tests/shared/test_streamable_http.py with an in-thread uvicorn helper that pre-binds the listening socket, eliminating the TOCTOU race... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| 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 | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width