From a77fb8faaec8ff4ae9ca3bc0e058ce5cdb96ec3a Mon Sep 17 00:00:00 2001 From: hansu650 <2788086371@qq.com> Date: Sun, 30 Aug 2026 19:25:55 +0800 Subject: [PATCH] Preserve worker failure exit code after reload --- src/hypercorn/run.py | 3 ++- tests/test_run.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/test_run.py diff --git a/src/hypercorn/run.py b/src/hypercorn/run.py index b83f4ca..fc501d2 100644 --- a/src/hypercorn/run.py +++ b/src/hypercorn/run.py @@ -103,7 +103,8 @@ def shutdown(*args: Any) -> None: for process in processes: process.terminate() - exitcode = _join_exited(processes) if exitcode != 0 else exitcode + if exitcode != 0: + _join_exited(processes) for sock in sockets.secure_sockets: sock.close() diff --git a/tests/test_run.py b/tests/test_run.py new file mode 100644 index 0000000..f02a2d1 --- /dev/null +++ b/tests/test_run.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from unittest.mock import Mock + +from _pytest.monkeypatch import MonkeyPatch + +import hypercorn.run +from hypercorn.config import Config + + +def test_run_preserves_reloaded_worker_failure_exitcode(monkeypatch: MonkeyPatch) -> None: + config = Config() + config.application_path = "example:app" + config.use_reloader = True + monkeypatch.setattr( + config, + "create_sockets", + Mock(return_value=Mock(secure_sockets=[], insecure_sockets=[])), + ) + + initial_process = Mock(exitcode=0, sentinel=object()) + failed_process = Mock(exitcode=1, sentinel=object()) + new_processes = [initial_process, failed_process] + + def populate(processes: list[Mock], *args: object) -> None: + processes.append(new_processes.pop(0)) + + context = Mock() + context.Event.return_value = Mock() + monkeypatch.setattr(hypercorn.run, "get_context", Mock(return_value=context)) + monkeypatch.setattr(hypercorn.run, "_populate", populate) + monkeypatch.setattr(hypercorn.run, "load_application", Mock()) + monkeypatch.setattr(hypercorn.run, "files_to_watch", Mock(return_value=set())) + monkeypatch.setattr( + hypercorn.run, + "check_for_updates", + Mock(side_effect=[True, False]), + ) + monkeypatch.setattr(hypercorn.run, "wait", Mock(side_effect=[set(), {failed_process.sentinel}])) + monkeypatch.setattr(hypercorn.run.signal, "signal", Mock()) + + assert hypercorn.run.run(config) == 1