From 6fe20c18125925fa8d3a842218b4eb50173e6ab6 Mon Sep 17 00:00:00 2001 From: huanghuoguoguo <1051233107@qq.com> Date: Sat, 16 May 2026 11:24:34 +0800 Subject: [PATCH] fix(core): handle sigint before app startup (#2189) --- src/langbot/pkg/core/boot.py | 4 +- tests/unit_tests/core/test_boot.py | 64 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/core/test_boot.py diff --git a/src/langbot/pkg/core/boot.py b/src/langbot/pkg/core/boot.py index 11a2d5e2..f866376b 100644 --- a/src/langbot/pkg/core/boot.py +++ b/src/langbot/pkg/core/boot.py @@ -46,12 +46,14 @@ async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application: async def main(loop: asyncio.AbstractEventLoop): + app_inst: app.Application | None = None try: # Hang system signal processing import signal def signal_handler(sig, frame): - app_inst.dispose() + if app_inst is not None: + app_inst.dispose() print('[Signal] Program exit.') os._exit(0) diff --git a/tests/unit_tests/core/test_boot.py b/tests/unit_tests/core/test_boot.py new file mode 100644 index 00000000..461458c0 --- /dev/null +++ b/tests/unit_tests/core/test_boot.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import signal +from types import SimpleNamespace + +import pytest + +from langbot.pkg.core import boot + + +@pytest.mark.asyncio +async def test_main_signal_handler_handles_sigint_before_app_created(monkeypatch): + captured_handler = {} + + def fake_signal(sig, handler): + captured_handler[sig] = handler + + async def fake_make_app(loop): + captured_handler[signal.SIGINT](signal.SIGINT, None) + + def fake_exit(code): + raise SystemExit(code) + + monkeypatch.setattr(signal, 'signal', fake_signal) + monkeypatch.setattr(boot, 'make_app', fake_make_app) + monkeypatch.setattr(boot.os, '_exit', fake_exit) + + with pytest.raises(SystemExit) as exc_info: + await boot.main(SimpleNamespace()) + + assert exc_info.value.code == 0 + + +@pytest.mark.asyncio +async def test_main_signal_handler_disposes_created_app(monkeypatch): + captured_handler = {} + app_inst = SimpleNamespace(disposed=False) + + def fake_signal(sig, handler): + captured_handler[sig] = handler + + def dispose(): + app_inst.disposed = True + + async def run(): + captured_handler[signal.SIGINT](signal.SIGINT, None) + + async def fake_make_app(loop): + app_inst.dispose = dispose + app_inst.run = run + return app_inst + + def fake_exit(code): + raise SystemExit(code) + + monkeypatch.setattr(signal, 'signal', fake_signal) + monkeypatch.setattr(boot, 'make_app', fake_make_app) + monkeypatch.setattr(boot.os, '_exit', fake_exit) + + with pytest.raises(SystemExit) as exc_info: + await boot.main(SimpleNamespace()) + + assert exc_info.value.code == 0 + assert app_inst.disposed is True