A worker thread cannot install a Python signal handler; keep registration under the main thread's owner.
Python signal handlers: install from the main interpreter thread
Operation contract
A worker attempts to replace the SIGINT handler and receives ValueError. The main thread installs an owned handler and immediately restores the previous handler. The program never sends SIGINT and does not change process state after completion.
Failure boundary
Python runs signal handlers in the main thread, but signal delivery timing is not a transaction boundary. Do not acquire an ordinary lock or perform complex cleanup inside a handler. Convert a signal into a simple shutdown request and let normal control flow close resources. Signal availability differs by operating system; the fixture checks registration only.
Working program
import signal
from threading import Thread
observed = []
def request_shutdown(signal_number, frame):
observed.append(signal_number)
def worker_registration():
try:
signal.signal(signal.SIGINT, request_shutdown)
except ValueError:
observed.append("worker_rejected")
worker = Thread(target=worker_registration)
worker.start()
worker.join()
previous = signal.signal(signal.SIGINT, request_shutdown)
signal.signal(signal.SIGINT, previous)
print("worker_rejected", "worker_rejected" in observed)
print("main_restored", signal.getsignal(signal.SIGINT) == previous)Output
worker_rejected True
main_restored TrueCosts and limits
Registration is constant-time; handler design matters more than installation cost. A process shutdown still needs bounded worker joins and explicit resource owners.
Common Mistakes
- Do not register Python handlers from a worker thread.
- Do not run lock-heavy application work inside a handler.
- Restoring a previous handler is necessary in a reusable process.
