fix(proxy): keep the health endpoint off the auth-store lock
`handle_health` called `adapter.is_authenticated()` inline from an `async def`. `UpstreamAdapter.is_authenticated` is documented as "Should be cheap — no network calls. Used by `proxy start` for a clear up-front error before binding a port." (`adapters/base.py`), and that is true of the `proxy start` preflight, which runs in a plain synchronous CLI function. It is not true on the event loop: `NousPortalAdapter.is_authenticated` goes through `_read_state()`, which takes `_auth_store_lock()` — the same cross-process lock with a 15s timeout as credential resolution — and `XAIGrokAdapter` reads its key pool off disk. `/health` is precisely what a supervisor, systemd unit, container healthcheck or load balancer polls, on a fixed interval, so it is the endpoint least able to afford a lock wait; and a wait here freezes every concurrent proxied stream, not just the healthcheck. Offload it with `asyncio.to_thread`. The response body is byte-identical; only the scheduling changes.
This commit is contained in:
@@ -112,11 +112,17 @@ def create_app(adapter: UpstreamAdapter) -> "web.Application":
|
||||
app[_adapter_key] = adapter
|
||||
|
||||
async def handle_health(request: "web.Request") -> "web.Response":
|
||||
# ``is_authenticated`` is documented as cheap (see UpstreamAdapter),
|
||||
# but both shipped adapters read auth state off disk, and the Nous one
|
||||
# does it under ``_auth_store_lock()`` — 15s cross-process. Offload it
|
||||
# so a healthcheck poll can never freeze the loop behind a lock held by
|
||||
# a concurrent ``hermes auth`` command.
|
||||
authenticated = await asyncio.to_thread(adapter.is_authenticated)
|
||||
return web.json_response(
|
||||
{
|
||||
"status": "ok",
|
||||
"upstream": adapter.display_name,
|
||||
"authenticated": adapter.is_authenticated(),
|
||||
"authenticated": authenticated,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user