fix(backend): stop forcing WindowsSelectorEventLoopPolicy so asyncio subprocesses work on Windows (#5937)

* fix(backend): stop forcing WindowsSelectorEventLoopPolicy on Windows

backend/sitecustomize.py switched every Python process with backend/ on
sys.path to WindowsSelectorEventLoopPolicy (added by #3236 for 'make dev'
on Windows). On Windows the selector loop cannot spawn asyncio
subprocesses - asyncio.create_subprocess_exec raises a bare
NotImplementedError - so ACP agents, stdio MCP servers, and local sandbox
command execution are all broken on native Windows, and the swallowed
exception surfaces as 'Error invoking ACP agent x: ' with no reason.

The forcing has no beneficiary left on Windows: the async postgres driver
is asyncpg (not psycopg, as #3236 states), and asyncpg 0.31.0 ships no
Windows wheels, so the postgres backend is unavailable on native Windows
regardless of the loop; the default SQLite backend (aiosqlite) runs on
either loop. The make dev startup fix in #3236 came from the serve.sh /
wait-for-port.sh changes, which are untouched.

With the Python-default ProactorEventLoop restored, subprocess features
work again on native Windows.

* fix(benchmark): scope a selector loop to the postgres checkpoint benchmark on Windows

Static review of the sitecustomize removal flagged the one caller that
relied on the old startup policy: bench_channels.py postgres workers run
bare asyncio.run() and enter AsyncPostgresSaver.from_conn_string(), which
the lockfile-pinned psycopg 3.3.3 rejects on Windows under
ProactorEventLoop before connecting.

Instead of restoring the global selector policy, the postgres branch of
the benchmark now scopes WindowsSelectorEventLoopPolicy to itself right
before asyncio.run(); memory/sqlite cases and every other backend process
keep the subprocess-capable Proactor default.
This commit is contained in:
xiaodu55 2026-09-27 15:07:39 +08:00 committed by GitHub
parent 5b8b69bd45
commit c368a9f229
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 18 additions and 26 deletions

View File

@ -629,6 +629,23 @@ async def _run_postgres_case(case: BenchmarkCase, messages: list[BaseMessage], u
await _delete_postgres_benchmark_thread(uri, thread_id)
def _configure_windows_postgres_loop_policy() -> None:
"""Keep psycopg's async connector usable on Windows for postgres cases.
psycopg rejects ProactorEventLoop before connecting on Windows, so the
postgres cases need the selector loop. Scoped to this entrypoint:
process-wide, the selector loop cannot spawn asyncio subprocesses, which
the rest of the backend requires.
"""
if sys.platform != "win32":
return
selector_policy = getattr(asyncio, "WindowsSelectorEventLoopPolicy", None)
if selector_policy is None:
return
if not isinstance(asyncio.get_event_loop_policy(), selector_policy):
asyncio.set_event_loop_policy(selector_policy())
def _run_case(case: BenchmarkCase, *, work_dir: Path) -> dict[str, Any]:
row = _base_row(case)
messages = [_message_for_update(index, case.payload_bytes) for index in range(case.update_count)]
@ -641,6 +658,7 @@ def _run_case(case: BenchmarkCase, *, work_dir: Path) -> dict[str, Any]:
uri = os.environ.get("TEST_POSTGRES_URI")
if not uri:
raise RuntimeError("postgres benchmark requires TEST_POSTGRES_URI")
_configure_windows_postgres_loop_policy()
measured = asyncio.run(_run_postgres_case(case, messages, uri))
reducer_writes = [[message] for message in messages]

View File

@ -1,26 +0,0 @@
"""Process-wide Python startup customizations for backend entrypoints.
When ``backend/`` is on ``sys.path``, Python imports this module during
interpreter startup. Keep changes here suitable for all gateway, script,
migration, and test entrypoints that run in that environment.
"""
from __future__ import annotations
import asyncio
import sys
def _configure_windows_event_loop_policy() -> None:
if sys.platform != "win32":
return
selector_policy = getattr(asyncio, "WindowsSelectorEventLoopPolicy", None)
if selector_policy is None:
return
if not isinstance(asyncio.get_event_loop_policy(), selector_policy):
asyncio.set_event_loop_policy(selector_policy())
_configure_windows_event_loop_policy()