fix(gateway): prevent Discord disconnects from blocking event loop

models_dev.py's fetch uses a synchronous requests.get(timeout=15). Called
from the async gateway message handlers, it blocked the event loop for up
to 15s, starving Discord heartbeats and causing ClientConnectionResetError
disconnects.

Adds get_model_context_length_async() which offloads the entire sync
resolution chain to a worker thread via asyncio.to_thread(), and switches
the two async gateway call sites (_prepare_inbound_message_text,
_handle_message_with_agent) to await it. The loop stays responsive; the
sync path remains the single source of truth for the cache.

Salvaged from PR #22753 by @itenev. Follow-up: dropped the unused
fetch_models_dev_async/lookup_models_dev_context_async aiohttp variants
from the original PR (dead code with zero callers that had drifted from
the sync cache logic) — the to_thread wrapper already runs the sync path
off-loop, so they were redundant.
This commit is contained in:
itenev 2026-07-01 02:00:16 -07:00 • committed by Teknium
parent d57a4c197c
commit f981d47cb0
3 changed files with 34 additions and 4 deletions

View file

@ -2155,6 +2155,35 @@ def get_model_context_length(
return DEFAULT_FALLBACK_CONTEXT
async def get_model_context_length_async(
model: str,
base_url: str = "",
api_key: str = "",
config_context_length: int | None = None,
provider: str = "",
custom_providers: list | None = None,
) -> int:
"""Async variant of get_model_context_length.
Offloads the entire synchronous resolution chain (which contains
blocking HTTP calls via ``requests``) to a background thread so it
does not freeze the asyncio event loop and cause Discord heartbeat
timeouts.
Shares all logic with the sync version — no code duplication.
"""
import asyncio
return await asyncio.to_thread(
get_model_context_length,
model,
base_url=base_url,
api_key=api_key,
config_context_length=config_context_length,
provider=provider,
custom_providers=custom_providers,
)
def estimate_tokens_rough(text: str) -> int:
"""Rough token estimate (~4 chars/token) for pre-flight checks.

View file

@ -9762,7 +9762,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if "@" in message_text:
try:
from agent.context_references import preprocess_context_references_async
from agent.model_metadata import get_model_context_length
from agent.model_metadata import get_model_context_length_async
_msg_cwd = os.environ.get("TERMINAL_CWD", os.path.expanduser("~"))
_msg_runtime = _resolve_runtime_agent_kwargs()
@ -9776,7 +9776,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
_msg_config_ctx = int(_msg_raw_ctx)
except Exception:
pass
_msg_ctx_len = get_model_context_length(
_msg_ctx_len = await get_model_context_length_async(
self._model,
base_url=self._base_url or _msg_runtime.get("base_url") or "",
api_key=_msg_runtime.get("api_key") or "",
@ -10114,7 +10114,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if history and len(history) >= 4:
from agent.model_metadata import (
estimate_messages_tokens_rough,
get_model_context_length,
get_model_context_length_async,
)
# Read model + compression config from config.yaml.
@ -10215,7 +10215,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
pass
if _hyg_compression_enabled:
_hyg_context_length = get_model_context_length(
_hyg_context_length = await get_model_context_length_async(
_hyg_model,
base_url=_hyg_base_url or "",
api_key=_hyg_api_key or "",

View file

@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
# Auto-extracted from noreply emails + manual overrides
AUTHOR_MAP = {
"5848605+itenev@users.noreply.github.com": "itenev", # PR #22753 salvage (asyncify model-context resolution in gateway message path so blocking requests.get can't starve Discord heartbeats)
"290873280+rrevenanttt@users.noreply.github.com": "rrevenanttt", # PR #40773 salvage (close hardline rm bypass via quoted paths and ${HOME} brace form)
"290871358+Vesna-9@users.noreply.github.com": "Vesna-9", # PR #41274 salvage (collapse shell line continuations before dangerous/hardline pattern matching so `rm -rf \<newline>/` can't bypass the yolo-proof hardline floor)
"214165399+kernel-t1@users.noreply.github.com": "kernel-t1", # PR #41349 salvage (.env sanitizer: only split when line starts with a known KEY= and preceding values are plain tokens; keep URL/query/whitespace secrets verbatim)