Skip to content
54 changes: 48 additions & 6 deletions simpervisor/atexitasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,69 @@

_handlers = []

_prev_handlers = {}
signal_handler_set = False


def add_handler(handler):
global signal_handler_set
if not signal_handler_set:
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)
signal_handler_set = True
"""
Adds a signal handler function that will be called when the Python process
receives either a SIGINT (on windows CTRL_C_EVENT) or SIGTERM signal.
"""
_ensure_signal_handlers_set()
_handlers.append(handler)


def remove_handler(handler):
"""Removes previously added signal handler."""
_handlers.remove(handler)


def _ensure_signal_handlers_set():
"""
Ensures _handle_signal is registered as a top level signal handler for
SIGINT and SIGTERM and saves previously registered non-default Python
callable signal handlers.
"""
global signal_handler_set
if not signal_handler_set:
# save previously registered non-default Python callable signal handlers
#
# windows note: signal.getsignal(signal.CTRL_C_EVENT) would error with
# "ValueError: signal number out of range", and
# signal.signal(signal.CTRL_C_EVENT, _handle_signal) would error with
# "ValueError: invalid signal value".
#
prev_sigint = signal.getsignal(signal.SIGINT)
prev_sigterm = signal.getsignal(signal.SIGTERM)
if callable(prev_sigint) and prev_sigint.__qualname__ != "default_int_handler":
_prev_handlers[signal.SIGINT] = prev_sigint
if callable(prev_sigterm) and prev_sigterm != signal.Handlers.SIG_DFL:
_prev_handlers[signal.SIGTERM] = prev_sigint

# let _handle_signal handle SIGINT and SIGTERM
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)
signal_handler_set = True


def _handle_signal(signum, *args):
"""
Calls functions added by add_handler, and then calls the previously
registered non-default Python callable signal handler if there were one.
"""
prev_handler = _prev_handlers.get(signum)

# Windows doesn't support SIGINT. Replacing it with CTRL_C_EVENT so that it
# can used with subprocess.Popen.send_signal
if signum == signal.SIGINT and sys.platform == "win32":
signum = signal.CTRL_C_EVENT

for handler in _handlers:
handler(signum)
sys.exit(0)

# call previously registered non-default Python callable handler or exit
if prev_handler:
prev_handler(signum, *args)
Comment on lines +72 to +74
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should get review and ideally tested on windows:

  • I made this prev_handler(signum, *args) instead of prev_handler(signum, None)`, is this right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*args will be frame argument. Not sure if it works on windows though. I think we can pass *args to handler.

else:
sys.exit(0)