fix(browser): suppress KeyboardInterrupt in atexit cleanup thread stop

A second Ctrl+C while the atexit hook joins the browser janitor thread
surfaced as "Exception ignored in atexit callback: _stop_browser_cleanup_thread"
with a KeyboardInterrupt traceback. The janitor is a daemon thread and the
interpreter is already exiting, so nothing is lost by swallowing the
interrupt — the terminal tool's sibling _stop_cleanup_thread already does.

Salvaged from #10765 (function has since moved to browser_tool_lifecycle.py).

Fixes #10764
Co-authored-by: LehaoLin <lehaolin98@outlook.com>
This commit is contained in:
LehaoLin
2026-04-16 13:13:32 +08:00
committed by Teknium
parent 7aa8249876
commit c59c1a98e3
2 changed files with 22 additions and 1 deletions

View File

@@ -187,3 +187,19 @@ class TestInactivityJanitorMultiplex:
assert "t1" not in self.bt._active_sessions
assert "t1" not in self.bt._session_last_activity
assert "t1" not in self.bt._cleanup_failures
class TestAtexitStopSwallowsInterrupt:
def test_second_ctrl_c_during_join_does_not_propagate(self, monkeypatch):
"""A second Ctrl+C while the atexit hook waits on the janitor must not escape as a
traceback (#10764): the thread is a daemon, the interpreter is already exiting."""
from tools import browser_tool
class _InterruptedJoin:
def join(self, timeout=None):
raise KeyboardInterrupt
monkeypatch.setattr(browser_tool, "_cleanup_thread", _InterruptedJoin())
monkeypatch.setattr(browser_tool, "_cleanup_running", True)
bt_lifecycle._stop_browser_cleanup_thread() # must not raise
assert browser_tool._cleanup_running is False

View File

@@ -424,7 +424,12 @@ def _stop_browser_cleanup_thread():
"""Stop the background cleanup thread."""
_bt._cleanup_running = False
if _bt._cleanup_thread is not None:
_bt._cleanup_thread.join(timeout=5)
# A second Ctrl+C during the timed join lands here as KeyboardInterrupt; the janitor is a
# daemon thread, so letting it propagate only prints "Exception ignored in atexit callback".
try:
_bt._cleanup_thread.join(timeout=5)
except (SystemExit, KeyboardInterrupt):
pass
def _update_session_activity(task_id: str):