Files
EvoScientist-Multi/EvoScientist/_winloop.py
T

51 lines
2.2 KiB
Python

"""Windows asyncio event-loop policy compatibility.
On Windows, MCP stdio servers are launched as subprocesses by the MCP SDK's
stdio transport, which uses ``anyio.open_process`` → ``asyncio`` async
subprocess support. ``asyncio``'s *Selector* event loop does **not** implement
async subprocess creation, so on a Selector loop the stdio transport falls back
to a synchronous ``subprocess.Popen`` inside an ``async`` function. Under
``langgraph dev`` (which enables ``blockbuster`` by default to police blocking
I/O) that synchronous call is flagged as a ``BlockingError`` — see issue #283.
The *Proactor* loop supports async subprocesses natively, so the fallback never
happens and ``blockbuster`` allows the (now genuinely async) spawn.
Windows has defaulted to ``WindowsProactorEventLoopPolicy`` since Python 3.8, so
this is normally a no-op. We set it explicitly anyway as a safeguard: a
dependency, IDE, or notebook host may have installed a Selector policy earlier
in the process, and the ``langgraph dev`` subprocess in particular runs code we
don't fully control. Calling this at each process entrypoint — **before any
event loop is created** — guarantees the MCP subprocess path stays async.
This must run at import/startup time, ahead of the first ``asyncio.run`` /
``new_event_loop`` call; once a loop exists, swapping the policy does not change
the already-running loop.
"""
from __future__ import annotations
import sys
def ensure_proactor_event_loop_policy() -> bool:
"""Install ``WindowsProactorEventLoopPolicy`` on Windows if needed.
Returns ``True`` if a Proactor policy is in effect afterwards (always
``False`` off Windows, where the concept doesn't apply). Safe and idempotent
to call multiple times; a no-op on non-Windows platforms.
"""
if sys.platform != "win32":
return False
import asyncio
proactor_policy = getattr(asyncio, "WindowsProactorEventLoopPolicy", None)
if proactor_policy is None: # pragma: no cover - non-Windows / stripped build
return False
current = asyncio.get_event_loop_policy()
if not isinstance(current, proactor_policy):
asyncio.set_event_loop_policy(proactor_policy())
return True