Describe the bug
The Desktop periodically calls setup.runtime_check to decide whether inference is ready. That RPC is routed through the shared _LONG_HANDLERS executor. If provider resolution becomes slow because of GIL pressure, a blocked keyring, OAuth refresh, or filesystem/auth lookup, every overlapping Desktop poll starts another provider-resolution call and occupies another worker in the shared RPC pool.
During a long tool-heavy session this can exhaust the shared pool and delay unrelated JSON-RPC responses. The Desktop then reports that inference is not ready even though credentials are configured and the provider itself is healthy.
Observed log pattern:
event loop stalled 20-30s (GIL pressure suspected)
ws write slow (loop stalled >10.0s)
setup.runtime_check ... timed out / failed
setup.status reports configured credentials, but runtime resolution still failed
Switching model providers does not resolve the symptom because the contention is inside the gateway process rather than at a specific provider.
Environment
- Windows 11
- Python 3.11
- Hermes Desktop / Hermes Agent 0.18.0
- Reproduced with multiple custom OpenAI-compatible providers
Root cause
setup.runtime_check is correctly routed away from the WebSocket reader thread by PR #57335, but the shared RPC executor does not deduplicate in-flight readiness checks. A slow check can therefore overlap with later 15-second Desktop polls. Each duplicate probe consumes a shared worker while resolving the same provider state.
A deterministic test can reproduce the problem by making resolve_runtime_provider() block and issuing overlapping setup.runtime_check requests. Current main invokes the resolver once per request instead of sharing the existing in-flight probe.
Expected behavior
- At most one runtime-resolution probe should be active per requested provider.
- Later polls should reuse the in-flight probe instead of consuming more shared RPC workers.
- A transient timeout should be treated as an unknown readiness result, not
ok=false, because setup.status may still authoritatively confirm that credentials are configured.
- Unrelated gateway RPCs should remain responsive.
Suggested fix
- Run provider resolution on a dedicated single-worker executor instead of recursively consuming the shared RPC pool.
- Keep one in-flight future per requested provider and reuse it for overlapping polls.
- Allow the first caller a bounded wait; while the probe is still running, return a retryable result without an
ok=false value.
- Clear the single-flight entry when the background probe completes.
This preserves the intent of #57335—keeping the WebSocket reader responsive—while preventing frontend polling from starving the executor that serves the rest of the gateway.
Describe the bug
The Desktop periodically calls
setup.runtime_checkto decide whether inference is ready. That RPC is routed through the shared_LONG_HANDLERSexecutor. If provider resolution becomes slow because of GIL pressure, a blocked keyring, OAuth refresh, or filesystem/auth lookup, every overlapping Desktop poll starts another provider-resolution call and occupies another worker in the shared RPC pool.During a long tool-heavy session this can exhaust the shared pool and delay unrelated JSON-RPC responses. The Desktop then reports that inference is not ready even though credentials are configured and the provider itself is healthy.
Observed log pattern:
Switching model providers does not resolve the symptom because the contention is inside the gateway process rather than at a specific provider.
Environment
Root cause
setup.runtime_checkis correctly routed away from the WebSocket reader thread by PR #57335, but the shared RPC executor does not deduplicate in-flight readiness checks. A slow check can therefore overlap with later 15-second Desktop polls. Each duplicate probe consumes a shared worker while resolving the same provider state.A deterministic test can reproduce the problem by making
resolve_runtime_provider()block and issuing overlappingsetup.runtime_checkrequests. Currentmaininvokes the resolver once per request instead of sharing the existing in-flight probe.Expected behavior
ok=false, becausesetup.statusmay still authoritatively confirm that credentials are configured.Suggested fix
ok=falsevalue.This preserves the intent of #57335—keeping the WebSocket reader responsive—while preventing frontend polling from starving the executor that serves the rest of the gateway.