OPEN SOURCE
software/src/orchestration/signals.py
Read on this site and download the files you need.
Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.
← Back to the open-source guide
1"""Graceful shutdown handling for fault-tolerant translation.23Provides signal handling to allow clean shutdown when the process4receives SIGINT (Ctrl+C) or SIGTERM, ensuring job state is saved.5"""67from __future__ import annotations89import signal10import threading11from types import FrameType12from typing import Any, Callable131415class GracefulShutdown:16"""Context manager for graceful shutdown handling.1718Captures SIGINT and SIGTERM signals and sets a flag that can be19checked by worker code to stop gracefully.2021Example:22with GracefulShutdown() as shutdown:23while not shutdown.should_stop:24# Do work...25process_next_verse()2627Attributes:28should_stop: True when shutdown has been requested.29signal_received: The signal number that was received, or None.30"""3132def __init__(self) -> None:33"""Initialize the graceful shutdown handler."""34self._should_stop = threading.Event()35self._signal_received: int | None = None36self._original_handlers: dict[int, Any] = {}37self._lock = threading.Lock()3839@property40def should_stop(self) -> bool:41"""Check if shutdown has been requested.4243Returns:44True if SIGINT or SIGTERM was received.45"""46return self._should_stop.is_set()4748@property49def signal_received(self) -> int | None:50"""Get the signal number that triggered shutdown.5152Returns:53Signal number (e.g., signal.SIGINT) or None if no signal received.54"""55return self._signal_received5657def request_stop(self) -> None:58"""Programmatically request a stop (without signal).5960Useful for testing or other shutdown triggers.61"""62self._should_stop.set()6364def wait_for_stop(self, timeout: float | None = None) -> bool:65"""Wait for shutdown signal.6667Args:68timeout: Maximum time to wait in seconds, or None for indefinite.6970Returns:71True if stop was signaled, False if timeout occurred.72"""73return self._should_stop.wait(timeout=timeout)7475def _signal_handler(self, signum: int, frame: FrameType | None) -> None:76"""Handle shutdown signals.7778Args:79signum: Signal number received.80frame: Current stack frame (unused).81"""82with self._lock:83if not self._should_stop.is_set():84self._signal_received = signum85self._should_stop.set()8687def __enter__(self) -> GracefulShutdown:88"""Enter the context manager and install signal handlers.8990Signal handlers can only be installed from the main thread.91When used from a non-main thread, the context manager still92works but without signal handling (should_stop can still be93set via request_stop()).9495Returns:96Self for use in context manager.97"""98if threading.current_thread() is threading.main_thread():99# Store original handlers100self._original_handlers[signal.SIGINT] = signal.signal(101signal.SIGINT, self._signal_handler102)103self._original_handlers[signal.SIGTERM] = signal.signal(104signal.SIGTERM, self._signal_handler105)106107return self108109def __exit__(110self,111exc_type: type | None,112exc_val: BaseException | None,113exc_tb: Any | None,114) -> bool:115"""Exit the context manager and restore signal handlers.116117Args:118exc_type: Exception type if an exception occurred.119exc_val: Exception value if an exception occurred.120exc_tb: Exception traceback if an exception occurred.121122Returns:123False to propagate any exception.124"""125# Restore original handlers126for signum, handler in self._original_handlers.items():127signal.signal(signum, handler)128129self._original_handlers.clear()130131return False132133134class ShutdownCallback:135"""Utility for registering shutdown callbacks.136137Allows registering cleanup functions to be called when shutdown occurs.138139Example:140shutdown = GracefulShutdown()141callbacks = ShutdownCallback(shutdown)142callbacks.register(save_state)143callbacks.register(close_connections)144145with shutdown:146while not shutdown.should_stop:147do_work()148149callbacks.run_all() # Called after exiting context150"""151152def __init__(self, shutdown: GracefulShutdown) -> None:153"""Initialize with a GracefulShutdown instance.154155Args:156shutdown: The shutdown handler to monitor.157"""158self._shutdown = shutdown159self._callbacks: list[Callable[[], None]] = []160self._lock = threading.Lock()161162def register(self, callback: Callable[[], None]) -> None:163"""Register a callback to run on shutdown.164165Args:166callback: Function to call during shutdown cleanup.167"""168with self._lock:169self._callbacks.append(callback)170171def run_all(self) -> list[Exception]:172"""Run all registered callbacks.173174Runs each callback, collecting any exceptions that occur.175All callbacks are attempted even if some fail.176177Returns:178List of exceptions that occurred (empty if all succeeded).179"""180exceptions = []181with self._lock:182for callback in self._callbacks:183try:184callback()185except Exception as e:186exceptions.append(e)187return exceptions188189190def ignore_signals_in_child() -> None:191"""Configure signal handling for child processes.192193Child processes in a multiprocessing pool should ignore194SIGINT/SIGTERM and let the parent handle shutdown coordination.195"""196signal.signal(signal.SIGINT, signal.SIG_IGN)197signal.signal(signal.SIGTERM, signal.SIG_IGN)198