Merge pull request #2555 from NousResearch/hermes/hermes-fdcb4c4a
fix(cli): prevent 'Press ENTER to continue...' on exit
This commit is contained in:
commit
2b3c1d81f0
2 changed files with 56 additions and 0 deletions
|
|
@ -1204,6 +1204,53 @@ _client_cache: Dict[tuple, tuple] = {}
|
||||||
_client_cache_lock = threading.Lock()
|
_client_cache_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _force_close_async_httpx(client: Any) -> None:
|
||||||
|
"""Mark the httpx AsyncClient inside an AsyncOpenAI client as closed.
|
||||||
|
|
||||||
|
This prevents ``AsyncHttpxClientWrapper.__del__`` from scheduling
|
||||||
|
``aclose()`` on a (potentially closed) event loop, which causes
|
||||||
|
``RuntimeError: Event loop is closed`` → prompt_toolkit's
|
||||||
|
"Press ENTER to continue..." handler.
|
||||||
|
|
||||||
|
We intentionally do NOT run the full async close path — the
|
||||||
|
connections will be dropped by the OS when the process exits.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from httpx._client import ClientState
|
||||||
|
inner = getattr(client, "_client", None)
|
||||||
|
if inner is not None and not getattr(inner, "is_closed", True):
|
||||||
|
inner._state = ClientState.CLOSED
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown_cached_clients() -> None:
|
||||||
|
"""Close all cached clients (sync and async) to prevent event-loop errors.
|
||||||
|
|
||||||
|
Call this during CLI shutdown, *before* the event loop is closed, to
|
||||||
|
avoid ``AsyncHttpxClientWrapper.__del__`` raising on a dead loop.
|
||||||
|
"""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
with _client_cache_lock:
|
||||||
|
for key, entry in list(_client_cache.items()):
|
||||||
|
client = entry[0]
|
||||||
|
if client is None:
|
||||||
|
continue
|
||||||
|
# Mark any async httpx transport as closed first (prevents __del__
|
||||||
|
# from scheduling aclose() on a dead event loop).
|
||||||
|
_force_close_async_httpx(client)
|
||||||
|
# Sync clients: close the httpx connection pool cleanly.
|
||||||
|
# Async clients: skip — we already neutered __del__ above.
|
||||||
|
try:
|
||||||
|
close_fn = getattr(client, "close", None)
|
||||||
|
if close_fn and not inspect.iscoroutinefunction(close_fn):
|
||||||
|
close_fn()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_client_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
def _get_cached_client(
|
def _get_cached_client(
|
||||||
provider: str,
|
provider: str,
|
||||||
model: str = None,
|
model: str = None,
|
||||||
|
|
@ -1222,6 +1269,7 @@ def _get_cached_client(
|
||||||
# "Event loop is closed" when httpx tries to clean up its
|
# "Event loop is closed" when httpx tries to clean up its
|
||||||
# transport. Discard the stale client and create a fresh one.
|
# transport. Discard the stale client and create a fresh one.
|
||||||
if cached_loop is not None and cached_loop.is_closed():
|
if cached_loop is not None and cached_loop.is_closed():
|
||||||
|
_force_close_async_httpx(cached_client)
|
||||||
del _client_cache[cache_key]
|
del _client_cache[cache_key]
|
||||||
else:
|
else:
|
||||||
return cached_client, model or cached_default
|
return cached_client, model or cached_default
|
||||||
|
|
|
||||||
8
cli.py
8
cli.py
|
|
@ -498,6 +498,14 @@ def _run_cleanup():
|
||||||
shutdown_mcp_servers()
|
shutdown_mcp_servers()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Close cached auxiliary LLM clients (sync + async) so that
|
||||||
|
# AsyncHttpxClientWrapper.__del__ doesn't fire on a closed event loop
|
||||||
|
# and trigger prompt_toolkit's "Press ENTER to continue..." handler.
|
||||||
|
try:
|
||||||
|
from agent.auxiliary_client import shutdown_cached_clients
|
||||||
|
shutdown_cached_clients()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue