Heavy sync init work (e.g. local ML models) starves stdio initialize/tool calls even with threadpool offload -- Windows GIL contention
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Anfängerfreundlichkeit
- 48/100
- Issue-Typ
- Dokumentation
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Ruhig
- Tech-Stack
- python
- Bereich
- backend-api-design, documentation
Rechercherichtung
Beginne mit der FastMCP-Dokumentation und dem im Bericht beschriebenen stdio-Initialisierungspfad; vergleiche die vorhandenen Hinweise zu anyio.to_thread mit dem beobachteten Windows-Verhalten. Unter „Done“ sollten dokumentierte Hinweise zur CPU-/GIL-gebundenen Initialisierung und zur Isolation von Subprozessen stehen, wobei jede initialize-Frist oder Entscheidung, etwas als out of scope zu behandeln, klar angegeben werden sollte.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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.
- Vorherrschende Sprache
- Python
- Sterne
- 24.3k
- Forks
- 4k
- Ø Merge
- 1 T. 11 Std.
- Gemergte PRs (30 T.)
- 30
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus modelcontextprotocol/python-sdk
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
modelcontextprotocol/python-sdk#3566 ·
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
modelcontextprotocol/python-sdk#3546 · 5 Kommentare ·
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
modelcontextprotocol/python-sdk#3545 · 1 Kommentar ·
-
v1 v2
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 91/100
modelcontextprotocol/python-sdk#3508 · 2 Kommentare ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 64/100
modelcontextprotocol/python-sdk#3504 ·
Alle Issues in modelcontextprotocol/python-sdk
Ähnliche Issues
-
essnmx good first issue
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 95/100
-
[Feature] 奇物选择添加优先级 Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
syfoud/Simulated_Scepter#174 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Giskard-AI/giskard-oss#2840 · 1 Kommentar ·
-
A claim comment carrying the issue number is silently declined while the workflow reports success Offenarea: repo bug perceived difficulty: 2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
yeti-platform/yeti#1380 ·