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:
briandevans
2026-08-12 20:40:10 -07:00
committed by kshitij
parent d4c2c74ece
commit 985b034a51
+7 -1
View File
@@ -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,
}
)