Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register existing signal handlers in simpervisor's atexit module #39

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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
consideRatio marked this conversation as resolved.
Show resolved Hide resolved

# let _handle_signal handle SIGINT and SIGTERM
signal.signal(signal.SIGINT, _handle_signal)
signal.signal(signal.SIGTERM, _handle_signal)
signal_handler_set = True
consideRatio marked this conversation as resolved.
Show resolved Hide resolved


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)