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)