Skip to content

Commit 49f1251

Browse files
committed
refactor: graceful_shutdown.py
1 parent a6242f9 commit 49f1251

File tree

3 files changed

+29
-36
lines changed

3 files changed

+29
-36
lines changed
Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
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
45
from types import FrameType
56

67
from src.metrics.logging import logging
@@ -9,35 +10,28 @@
910
logger = logging.getLogger(__name__)
1011

1112

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-
2513
@contextmanager
2614
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)
3737
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})

src/modules/oracles/common/runtime.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,15 @@ def build_staking_module_web3(module_name: str) -> Web3StakingModule:
125125

126126
def run_oracle_module(module: OracleModule):
127127
module.check_contract_configs()
128+
128129
try:
129130
module.w3.telemetry_data_bus.send_telemetry(TelemetryEventId.ORACLE_STARTUP)
130131
except Exception:
131132
logger.warning({'msg': 'Failed to send startup telemetry to DataBus.'}, exc_info=True)
133+
132134
try:
133-
# Docker sends SIGTERM on `docker stop`; convert it to SystemExit so `finally`
134-
# runs module.shutdown() and cleanup is not skipped.
135+
# Convert termination signals to SystemExit so regular cleanup in
136+
# `finally` still runs during process shutdown.
135137
with graceful_shutdown_signal_handlers():
136138
if variables.DAEMON:
137139
module.run_as_daemon()

tests/modules/submodules/test_oracle_module.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import signal
21
from http import HTTPStatus
32
from unittest.mock import MagicMock, Mock, patch
43

@@ -143,7 +142,6 @@ def test_run_oracle_module__single_cycle__calls_shutdown(oracle: OracleModule, m
143142
oracle.shutdown = Mock()
144143

145144
with (
146-
patch("src.modules.common.graceful_shutdown.signal.getsignal", return_value=signal.SIG_DFL),
147145
patch("src.modules.common.graceful_shutdown.signal.signal"),
148146
):
149147
run_oracle_module(oracle)
@@ -162,7 +160,6 @@ def test_run_oracle_module__daemon_exit__calls_shutdown(oracle: OracleModule, mo
162160
oracle.shutdown = Mock()
163161

164162
with (
165-
patch("src.modules.common.graceful_shutdown.signal.getsignal", return_value=signal.SIG_DFL),
166163
patch("src.modules.common.graceful_shutdown.signal.signal"),
167164
pytest.raises(SystemExit, match="0"),
168165
):

0 commit comments

Comments
 (0)