Skip to content

Commit d2ddd95

Browse files
authored
Cancel Supervisor startup task on early shutdown signal (#6175)
When receiving a shutdown signal during startup, the Supervisor should cancel its startup task to ensure a graceful shutdown. This prevents Supervisor accidentally accessing the Event loop after it has been closed by the stop procedure: RuntimeError: Event loop stopped before Future completed.
1 parent ac9947d commit d2ddd95

File tree

2 files changed

+22
-12
lines changed

2 files changed

+22
-12
lines changed

supervisor/__main__.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,23 @@ def run_os_startup_check_cleanup() -> None:
6666
_LOGGER.info("Setting up Supervisor")
6767
loop.run_until_complete(coresys.core.setup())
6868

69-
bootstrap.register_signal_handlers(loop, coresys)
69+
# Create startup task that can be cancelled gracefully
70+
startup_task = loop.create_task(coresys.core.start())
71+
72+
def shutdown_handler() -> None:
73+
"""Handle shutdown signals gracefully during startup."""
74+
if not startup_task.done():
75+
_LOGGER.warning("Supervisor startup interrupted by shutdown signal")
76+
startup_task.cancel()
77+
78+
coresys.create_task(coresys.core.stop())
79+
80+
bootstrap.register_signal_handlers(loop, shutdown_handler)
7081

7182
try:
72-
loop.run_until_complete(coresys.core.start())
83+
loop.run_until_complete(startup_task)
84+
except asyncio.CancelledError:
85+
_LOGGER.warning("Supervisor startup cancelled")
7386
except Exception as err: # pylint: disable=broad-except
7487
# Supervisor itself is running at this point, just something didn't
7588
# start as expected. Log with traceback to get more insights for

supervisor/bootstrap.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# ruff: noqa: T100
44
import asyncio
5+
from collections.abc import Callable
56
from importlib import import_module
67
import logging
78
import os
@@ -289,26 +290,22 @@ def check_environment() -> None:
289290
_LOGGER.critical("Can't find Docker socket!")
290291

291292

292-
def register_signal_handlers(loop: asyncio.AbstractEventLoop, coresys: CoreSys) -> None:
293+
def register_signal_handlers(
294+
loop: asyncio.AbstractEventLoop, shutdown_handler: Callable[[], None]
295+
) -> None:
293296
"""Register SIGTERM, SIGHUP and SIGKILL to stop the Supervisor."""
294297
try:
295-
loop.add_signal_handler(
296-
signal.SIGTERM, lambda: loop.create_task(coresys.core.stop())
297-
)
298+
loop.add_signal_handler(signal.SIGTERM, shutdown_handler)
298299
except (ValueError, RuntimeError):
299300
_LOGGER.warning("Could not bind to SIGTERM")
300301

301302
try:
302-
loop.add_signal_handler(
303-
signal.SIGHUP, lambda: loop.create_task(coresys.core.stop())
304-
)
303+
loop.add_signal_handler(signal.SIGHUP, shutdown_handler)
305304
except (ValueError, RuntimeError):
306305
_LOGGER.warning("Could not bind to SIGHUP")
307306

308307
try:
309-
loop.add_signal_handler(
310-
signal.SIGINT, lambda: loop.create_task(coresys.core.stop())
311-
)
308+
loop.add_signal_handler(signal.SIGINT, shutdown_handler)
312309
except (ValueError, RuntimeError):
313310
_LOGGER.warning("Could not bind to SIGINT")
314311

0 commit comments

Comments
 (0)