modelcontextprotocol / modelcontextprotocol/python-sdk
Heavy sync init work (e.g. local ML models) starves stdio initialize/tool calls even with threadpool offload -- Windows GIL contention
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 24.3k
- Fork
- 4k
- Merge trung bình
- 1 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 31
Mô tả
Environment
mcp1.28.1 (Python SDK,mcp.server.fastmcp.FastMCP)- stdio transport, Windows 11
- Blocking work:
sentence-transformers/ torch (local embedding model), but this
generalizes to any CPU/GIL-bound library load (onnxruntime, local LLM inference, etc.)
Summary
Related to #1839 / #1909, but a distinct failure mode that threadpool offload does not
fix: GIL-bound CPU work (loading a torch model) contends with the anyio stdio
transport's Windows pipe-reader thread for the GIL, regardless of where it runs in-process.
This makes several reasonable-looking designs fail in ways that are easy to ship and hard
to reproduce in a quick manual test.
Measurements
Same model load (sentence-transformers/all-MiniLM-L6-v2), only the location changes:
| Where the load runs | initialize |
First tool call |
|---|---|---|
| Standalone script, no event loop | — | ~8s |
Synchronously before mcp.run() ("warm on start") |
blocks 8–70s | instant |
In a background thread, started before mcp.run() |
delayed | deadlocks |
Lazily on first call, offloaded via anyio.to_thread / threadpool |
instant | ~70s |
| In a separate subprocess, talked to over a pipe | ~1s | ~8–9s |
Only the subprocess isolation keeps both connection time and first-call time bounded.
The variance (8s → 70s) is driven by contention between the model-loading thread and
anyio's Windows stdio reader thread — both fighting for the GIL.
Why this matters beyond my case
It's tempting to "fix" a slow first tool call by warming eagerly before mcp.run().
That instead moves the stall onto the initialize handshake — which is worse, because a
client that doesn't get the handshake in time just drops the server with no visible
error. That's a silent failure (tools missing, no exception) that's intermittent
(fine when warm/cached, broken on a cold start), so it's easy to ship and hard to catch
in CI or a quick manual check.
Suggested fix / ask
Not asking for an SDK-level fix necessarily — run_in_threadpool from #1909 is the right
answer for I/O-bound blocking calls. But it'd help other implementers to:
- Note in the docs/guidance for
FastMCPthat CPU/GIL-bound initialization (local model
loads, etc.) should NOT be threadpool-offloaded in-process — it should run in a
separate subprocess — since threadpool offload does not release the GIL contention
the way it does for I/O-bound blocking calls. - Consider whether
initializeshould have an explicit, documented deadline/backpressure
behavior so implementers know exactly how much startup latency is safe.
Disclaimer: I'm not a Python/asyncio expert — this diagnosis, the measurements, and
this write-up were produced by Claude Code (Anthropic's coding agent) while it was
building a local memory/retrieval MCP server for me and debugging why it intermittently
failed to connect. I'm filing it because the finding looked substantive and reproducible,
but I likely can't answer deep follow-up questions about the internals myself.
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với tài liệu FastMCP và đường dẫn khởi tạo stdio được mô tả trong báo cáo; so sánh hướng dẫn hiện có về anyio.to_thread với hành vi quan sát được trên Windows. Phần Done cần ghi lại hướng dẫn cho việc khởi tạo bị giới hạn bởi CPU/GIL và cô lập subprocess, đồng thời nêu rõ mọi thời hạn của initialize hoặc quyết định đưa nội dung nào đó ra ngoài phạm vi.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend-api-design, documentation
- Loại issue
- Tài liệu
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 48/100