|
1 | 1 | import signal |
2 | | -from collections.abc import Callable, Iterator |
3 | | -from contextlib import contextmanager |
| 2 | +import threading |
| 3 | +from collections.abc import Iterator |
| 4 | +from contextlib import ExitStack, contextmanager |
4 | 5 | from types import FrameType |
5 | 6 |
|
6 | 7 | from src.metrics.logging import logging |
|
9 | 10 | logger = logging.getLogger(__name__) |
10 | 11 |
|
11 | 12 |
|
12 | | -type SignalHandler = Callable[[int, FrameType | None], object] | int | None |
13 | | - |
14 | | - |
15 | | -def _shutdown_signal_handler(signum: int, _frame: FrameType | None) -> None: |
16 | | - try: |
17 | | - signal_name = signal.Signals(signum).name |
18 | | - except ValueError: |
19 | | - signal_name = str(signum) |
20 | | - |
21 | | - logger.info({'msg': 'Received shutdown signal. Requesting graceful exit.', 'signal': signal_name}) |
22 | | - raise SystemExit(0) |
23 | | - |
24 | | - |
25 | 13 | @contextmanager |
26 | 14 | def graceful_shutdown_signal_handlers() -> Iterator[None]: |
27 | | - previous_handlers: dict[signal.Signals, SignalHandler] = {} |
28 | | - |
29 | | - for stop_signal in (signal.SIGINT, signal.SIGTERM): |
30 | | - try: |
31 | | - previous_handlers[stop_signal] = signal.getsignal(stop_signal) |
32 | | - signal.signal(stop_signal, _shutdown_signal_handler) |
33 | | - except ValueError: |
34 | | - logger.info({'msg': 'Cannot register signal handler outside main thread', 'signal': stop_signal}) |
35 | | - |
36 | | - try: |
| 15 | + """ |
| 16 | + Temporarily convert `SIGINT` and `SIGTERM` to `SystemExit(0)`. |
| 17 | +
|
| 18 | + Use this around a top-level module run loop so regular `finally` cleanup |
| 19 | + still runs on process shutdown. The previous handlers are restored when the |
| 20 | + context exits. |
| 21 | + """ |
| 22 | + if threading.current_thread() is not threading.main_thread(): |
| 23 | + logger.info({'msg': 'Cannot manage signal handlers outside main thread'}) |
| 24 | + yield |
| 25 | + return |
| 26 | + |
| 27 | + def shutdown_signal_handler(signum: int, _frame: FrameType | None) -> None: |
| 28 | + logger.info( |
| 29 | + {'msg': 'Received shutdown signal. Requesting graceful exit.', 'signal': signal.Signals(signum).name} |
| 30 | + ) |
| 31 | + raise SystemExit(0) |
| 32 | + |
| 33 | + with ExitStack() as stack: |
| 34 | + for stop_signal in (signal.SIGINT, signal.SIGTERM): |
| 35 | + previous_handler = signal.signal(stop_signal, shutdown_signal_handler) |
| 36 | + stack.callback(signal.signal, stop_signal, previous_handler) |
37 | 37 | yield |
38 | | - finally: |
39 | | - for stop_signal, previous_handler in previous_handlers.items(): |
40 | | - try: |
41 | | - signal.signal(stop_signal, previous_handler) |
42 | | - except ValueError: |
43 | | - logger.info({'msg': 'Cannot restore signal handler outside main thread', 'signal': stop_signal}) |
0 commit comments