fix(desktop): keep cold gateway config off event loop

This commit is contained in:
wayne1992127 2026-07-27 23:20:40 +08:00 committed by kshitij
parent fe9bdd17e3
commit 579672d87d
2 changed files with 50 additions and 6 deletions

View File

@ -2953,6 +2953,20 @@ def _collect_profile_gateway_topology_cached() -> Dict[str, Any]:
return data
def _load_configured_gateway_platforms() -> set[str]:
"""Load connected platform names away from the asyncio event loop.
The first ``load_gateway_config()`` call performs platform discovery and
can take longer than Desktop's WebSocket connect timeout on Windows. This
helper is synchronous by design; ``get_status`` runs it in Starlette's
worker pool so a concurrent ``/api/ws`` handshake can still complete.
"""
from gateway.config import load_gateway_config
gateway_config = load_gateway_config()
return {platform.value for platform in gateway_config.get_connected_platforms()}
@app.get("/api/ssh/ownership")
async def get_ssh_ownership(request: Request):
_require_token(request)
@ -3054,12 +3068,9 @@ async def get_status(profile: Optional[str] = None):
gateway_updated_at = None
configured_gateway_platforms: set[str] | None = None
try:
from gateway.config import load_gateway_config
gateway_config = load_gateway_config()
configured_gateway_platforms = {
platform.value for platform in gateway_config.get_connected_platforms()
}
configured_gateway_platforms = await run_in_threadpool(
_load_configured_gateway_platforms
)
except Exception:
configured_gateway_platforms = None

View File

@ -318,6 +318,39 @@ class TestWebServerEndpoints:
monitor.close()
writer.close()
def test_get_status_loads_gateway_config_off_event_loop(self, monkeypatch):
"""Cold gateway config loading must not block the WebSocket loop.
On Windows the first ``load_gateway_config()`` call imports and
discovers platform adapters and can take longer than Desktop's 15s
WebSocket timeout. Running it inline makes a concurrent /api/ws
handshake time out before ``gateway.ready`` can be sent.
"""
import gateway.config as gateway_config
import hermes_cli.web_server as web_server
seen = {}
class _Config:
@staticmethod
def get_connected_platforms():
return []
def _load():
seen["thread"] = threading.get_ident()
return _Config()
monkeypatch.setattr(gateway_config, "load_gateway_config", _load)
async def _run():
event_loop_thread = threading.get_ident()
await web_server.get_status()
return event_loop_thread
event_loop_thread = asyncio.run(_run())
assert seen["thread"] != event_loop_thread
def test_get_sessions_auto_archive_uses_maintenance_writer(self):
from hermes_cli import web_server
from hermes_cli.config import load_config, save_config