fix(gateway): hard-exit on KeyboardInterrupt path too

The KeyboardInterrupt handler in run_gateway() was the only exit path
that still used bare 'return' instead of _hard_exit_after_gateway_teardown().
While less common than service-managed restarts, a console Ctrl+C still
leaves the process vulnerable to the same Python finalization hang on
non-daemon worker threads (cron ThreadPoolExecutor jobs). Route it through
the same backstop, with a 'return' guard for test stubs that don't raise
on code 0 (production os._exit never returns).
This commit is contained in:
kshitijk4poor 2026-07-22 00:43:48 +05:30 committed by kshitij
parent c9ab8baf31
commit 4425ddd94d
3 changed files with 26 additions and 1 deletions

View file

@ -4998,7 +4998,8 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False, fo
traceback=_traceback.format_exc(),
)
print("\nGateway stopped.")
return
_hard_exit_after_gateway_teardown(0)
return # unreachable in production (os._exit); guard for test stubs
except SystemExit as e:
_exit_diag(
"asyncio.run.SystemExit",

View file

@ -63,6 +63,10 @@ def test_run_gateway_exits_cleanly_on_keyboard_interrupt(monkeypatch, capsys):
_install_fake_gateway_run(monkeypatch, fake_start_gateway)
monkeypatch.setattr(gateway.asyncio, "run", fake_asyncio_run)
# KeyboardInterrupt now uses the same hard-exit backstop as all other
# exit paths (instead of a bare ``return``). The test stub's
# _exit_after_graceful_shutdown is a no-op for code 0, so run_gateway()
# returns normally — but the real implementation would call os._exit(0).
gateway.run_gateway()
out = capsys.readouterr().out

View file

@ -85,3 +85,23 @@ def test_run_gateway_hard_exits_after_failed_return(monkeypatch):
gateway_cli.run_gateway()
assert excinfo.value.code == 1
def test_run_gateway_hard_exits_after_keyboard_interrupt(monkeypatch):
"""KeyboardInterrupt (console Ctrl+C) must also hard-exit, not return.
A bare ``return`` would let Python finalization join non-daemon worker
threads, the same wedge this backstop prevents on the other exit paths.
"""
gateway_cli = _prepare(monkeypatch)
def _fake_run(coro):
coro.close()
raise KeyboardInterrupt()
monkeypatch.setattr(gateway_cli.asyncio, "run", _fake_run)
with pytest.raises(_HardExitObserved) as excinfo:
gateway_cli.run_gateway()
assert excinfo.value.code == 0