From 3e9db96dd9fe0a28dacdc970576b8f4b068a9e03 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 8 Jun 2026 21:23:50 +0100 Subject: [PATCH 1/3] Fix gunicorn endless restarting --- CHANGES/6968.bugfix.rst | 1 + aiohttp/worker.py | 24 ++++++++++++++++++------ tests/test_worker.py | 28 ++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 CHANGES/6968.bugfix.rst diff --git a/CHANGES/6968.bugfix.rst b/CHANGES/6968.bugfix.rst new file mode 100644 index 00000000000..b66a177a72f --- /dev/null +++ b/CHANGES/6968.bugfix.rst @@ -0,0 +1 @@ +Fixed ``GunicornWebWorker`` endlessly respawning when app fails during startup -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/worker.py b/aiohttp/worker.py index 25dfd284c6b..532b989f24f 100644 --- a/aiohttp/worker.py +++ b/aiohttp/worker.py @@ -49,14 +49,20 @@ def init_process(self) -> None: super().init_process() def run(self) -> None: - self._task = self.loop.create_task(self._run()) + # base.Worker.init_process() sets self.booted = True before + # invoking run(), but for the aiohttp worker the real boot work + # (factory call, runner setup, binding sockets) happens here. + # Reset until _run() reaches the serve loop so that the arbiter + # can tell a startup failure from a normal worker exit and + # halt instead of endlessly respawning workers. + self.booted = False - try: # ignore all finalization problems + self._task = self.loop.create_task(self._run()) + try: self.loop.run_until_complete(self._task) - except Exception: - self.log.exception("Exception in gunicorn worker") - self.loop.run_until_complete(self.loop.shutdown_asyncgens()) - self.loop.close() + finally: + self.loop.run_until_complete(self.loop.shutdown_asyncgens()) + self.loop.close() sys.exit(self.exit_code) @@ -106,6 +112,12 @@ async def _run(self) -> None: ) await site.start() + # Sockets are bound; tell the arbiter the worker is ready to + # accept requests. Any failure before this point propagates out + # of run() with self.booted=False so the arbiter exits with + # WORKER_BOOT_ERROR instead of treating this as a clean exit. + self.booted = True + # If our parent changed then we shut down. pid = os.getpid() try: diff --git a/tests/test_worker.py b/tests/test_worker.py index 561453fc34d..89c8ef55bef 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -123,9 +123,33 @@ def test_run_not_app( worker.loop = event_loop worker.wsgi = "not-app" worker.alive = False - with pytest.raises(SystemExit): + with pytest.raises(RuntimeError, match="wsgi app should be"): + worker.run() + assert not worker.booted + assert event_loop.is_closed() + + +def test_run_on_startup_raises( + worker: base_worker.GunicornWebWorker, event_loop: asyncio.AbstractEventLoop +) -> None: + worker.log = mock.Mock() + worker.cfg = mock.Mock() + worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT + worker.cfg.is_ssl = False + worker.cfg.graceful_timeout = 100 + worker.sockets = [] + + app = web.Application() + + async def boom(app: web.Application) -> None: + raise RuntimeError("boom during startup") + + app.on_startup.append(boom) + worker.wsgi = app + worker.loop = event_loop + with pytest.raises(RuntimeError, match="boom during startup"): worker.run() - worker.log.exception.assert_called_with("Exception in gunicorn worker") + assert not worker.booted assert event_loop.is_closed() From 08aec8dc2427b703b2f1d9165b49a0a5c698734a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 8 Jun 2026 21:30:45 +0100 Subject: [PATCH 2/3] Rename 6968.bugfix.rst to 12879.bugfix.rst --- CHANGES/{6968.bugfix.rst => 12879.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGES/{6968.bugfix.rst => 12879.bugfix.rst} (100%) diff --git a/CHANGES/6968.bugfix.rst b/CHANGES/12879.bugfix.rst similarity index 100% rename from CHANGES/6968.bugfix.rst rename to CHANGES/12879.bugfix.rst From 8dfcf6a880e63688b0411d090b699077be0c3083 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 8 Jun 2026 21:39:24 +0100 Subject: [PATCH 3/3] Update 12879.bugfix.rst --- CHANGES/12879.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/12879.bugfix.rst b/CHANGES/12879.bugfix.rst index b66a177a72f..9dc8057a64d 100644 --- a/CHANGES/12879.bugfix.rst +++ b/CHANGES/12879.bugfix.rst @@ -1 +1 @@ -Fixed ``GunicornWebWorker`` endlessly respawning when app fails during startup -- by :user:`Dreamsorcerer`. +Fixed ``GunicornWebWorker`` endlessly reloading when app fails during startup -- by :user:`Dreamsorcerer`.