Python installs its own SIGINT handler in order to raise KeyboardInterrupt exceptions. Setting the signal to SIG_DFL will not restore that handler, but the "standard" handler of the system itself (which terminates the interpreter).

You have to store the original handler and restore that handler when you're done:

original_sigint_handler = signal.getsignal(signal.SIGINT)

# Then, later...
signal.signal(signal.SIGINT, original_sigint_handler)

As kindall rightfully says in comments, you can express this as a context manager:

from contextlib import contextmanager

@contextmanager
def sigint_ignored():
    original_sigint_handler = signal.getsignal(signal.SIGINT)
    signal.signal(signal.SIGINT, signal.SIG_IGN)
    try:
        print('Now ignoring CTRL-C')
        yield
    except:
        raise  # Exception is dropped if we don't reraise it.
    finally:
        print('Returning control to default signal handler')
        signal.signal(signal.SIGINT, original_sigint_handler)

You can use it like this:

# marker 1
print('No signal handler modifications yet')
print('Sleeping...')
time.sleep(10)

# marker 2
with sigint_ignored():
    print('Sleeping...')
    time.sleep(10)

# marker 3
print('Sleeping...')
time.sleep(10)
Answer from Frédéric Hamidi on Stack Overflow
🌐
Python
docs.python.org › 3 › library › signal.html
signal — Set handlers for asynchronous events
The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
Discussions

[asyncio] Skipping signal handling setup during import for Python embedded context - Ideas - Discussions on Python.org
Python supports initializing the interpreter with signal handling disabled: Initialization, Finalization, and Threads — Python 3.12.0 documentation As the documentation states, this might be useful when Python is embedded. Personally, I think its entirely reasonable that, within this embedded ... More on discuss.python.org
🌐 discuss.python.org
1
October 24, 2023
Signal handler is registered, but process still exits abruptly on Ctrl+C (implementing graceful shutdown)
According to the docs, Asyncio.run() registers SIGINT handler manually in order to not hang program. https://docs.python.org/3/library/asyncio-runner.html#asyncio.run Here is relevant fragment: When signal.SIGINT is raised by Ctrl-C, KeyboardInterrupt exception is raised in the main thread by default. However this doesn’t work with asyncio because it can interrupt asyncio internals and can hang the program from exiting. To mitigate this issue, asyncio handles signal.SIGINT as follows: asyncio.Runner.run() installs a custom signal.SIGINT handler before any user code is executed and removes it when exiting from the function. The Runner creates the main task for the passed coroutine for its execution. When signal.SIGINT is raised by Ctrl-C, the custom signal handler cancels the main task by calling asyncio.Task.cancel() which raises asyncio.CancelledError inside the main task. This causes the Python stack to unwind, try/except and try/finally blocks can be used for resource cleanup. After the main task is cancelled, asyncio.Runner.run() raises KeyboardInterrupt . A user could write a tight loop which cannot be interrupted by asyncio.Task.cancel() , in which case the second following Ctrl-C immediately raises the KeyboardInterrupt without cancelling the main task. So if you want to make your code resistant to sigint, you have to clear any signal handlers at the begining of your job: https://stackoverflow.com/questions/22916783/reset-python-sigint-to-default-signal-handler But that might be a little overcomplicated. It would be much easier to implement a job proccessor as a separate thread, without asyncio interference. More on reddit.com
🌐 r/learnpython
4
6
September 4, 2024
scope - Handling signals in Python inside a function - Code Review Stack Exchange
I have code that needs to exit gracefully. To simplify things, I am presenting an example based on the answer given here. Since I need to handle a few signals, I thought of putting this logic in a More on codereview.stackexchange.com
🌐 codereview.stackexchange.com
June 16, 2014
Improved signal handling initialization for embedded Python
Feature or enhancement Proposal: If Python has been initialized via Py_InitializeEx(0); then importing signal as import signal should by default skip setting up signal handlers. I understand that t... More on github.com
🌐 github.com
2
October 27, 2023
🌐
University of New Brunswick
cs.unb.ca › ~bremner › teaching › cs2613 › books › python3-doc › library › signal.html
signal — Set handlers for asynchronous events — Python 3.9.2 documentation
The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
🌐
Program Creek
programcreek.com › python
Python remove signal handler
""" self._check_signal(sig) try: ... (ValueError, OSError) as exc: logger.info('set_wakeup_fd(-1) failed: %s', exc) return True ... def remove_signal_handler(self, sig): """Remove a handler for a signal....
🌐
Python.org
discuss.python.org › ideas
[asyncio] Skipping signal handling setup during import for Python embedded context - Ideas - Discussions on Python.org
October 24, 2023 - Python supports initializing the interpreter with signal handling disabled: Initialization, Finalization, and Threads — Python 3.12.0 documentation As the documentation states, this might be useful when Python is embedded. Personally, I think its entirely reasonable that, within this embedded context, an application may choose to import and run asyncio.
🌐
Reddit
reddit.com › r/learnpython › signal handler is registered, but process still exits abruptly on ctrl+c (implementing graceful shutdown)
r/learnpython on Reddit: Signal handler is registered, but process still exits abruptly on Ctrl+C (implementing graceful shutdown)
September 4, 2024 -

Hello, I have a job processor written in Python. It's a loop that pulls a job from a database, does stuff with it inside a transaction with row-level locking, then writes the result back to the database. Jobs are relatively small, usually shorter than 5s.

import asyncio
import signal

running = True

def signal_handler(sig, frame):
    global running
    print("SIGINT received, stopping on next occasion")
    running = False

signal.signal(signal.SIGINT, signal_handler)
while running:
    asyncio.run(do_one_job()) # imported from a module

I would expect the above code to work. But when Ctrl+Cing the process, the current job stops abruptly with a big stack trace and an exception from one of the libraries used indirectly from do_one_job (urllib.ProtocolError: Connection aborted). The whole point of my signal handling is to avoid interrupting a job while it's running. While jobs are processed within transactions and shouldn't break the DB's consistency, I'd rather have an additional layer of safety by trying to wait until they are properly finished, especially since they're short.

Why can do_one_job() observe a signal that's supposed to be already handled? How can I implement graceful shutdown in Python?

Top answer
1 of 2
3
According to the docs, Asyncio.run() registers SIGINT handler manually in order to not hang program. https://docs.python.org/3/library/asyncio-runner.html#asyncio.run Here is relevant fragment: When signal.SIGINT is raised by Ctrl-C, KeyboardInterrupt exception is raised in the main thread by default. However this doesn’t work with asyncio because it can interrupt asyncio internals and can hang the program from exiting. To mitigate this issue, asyncio handles signal.SIGINT as follows: asyncio.Runner.run() installs a custom signal.SIGINT handler before any user code is executed and removes it when exiting from the function. The Runner creates the main task for the passed coroutine for its execution. When signal.SIGINT is raised by Ctrl-C, the custom signal handler cancels the main task by calling asyncio.Task.cancel() which raises asyncio.CancelledError inside the main task. This causes the Python stack to unwind, try/except and try/finally blocks can be used for resource cleanup. After the main task is cancelled, asyncio.Runner.run() raises KeyboardInterrupt . A user could write a tight loop which cannot be interrupted by asyncio.Task.cancel() , in which case the second following Ctrl-C immediately raises the KeyboardInterrupt without cancelling the main task. So if you want to make your code resistant to sigint, you have to clear any signal handlers at the begining of your job: https://stackoverflow.com/questions/22916783/reset-python-sigint-to-default-signal-handler But that might be a little overcomplicated. It would be much easier to implement a job proccessor as a separate thread, without asyncio interference.
2 of 2
2
I can't reproduce using simple code. The following works correctly for me: import asyncio import signal from random import randint running = True def signal_handler(sig, frame): global running print("SIGINT received, stopping on next occasion") running = False async def do_one_job(): print("Starting a job", end="", flush=True) for _ in range(randint(5, 25)): print(".", end="", flush=True) await asyncio.sleep(0.25) print("Done") signal.signal(signal.SIGINT, signal_handler) while running: asyncio.run(do_one_job()) (Although I don't think it's good to call asyncio.run over and over again. I would rewrite such that it's called once on a main() and that the while loop repeats inside that with await do_one_job()). But still, it works for me. There must be something rewriting the signal handling beyond asyncio, or perhaps try different versions of Python?
🌐
NV5 Geospatial Software
nv5geospatialsoftware.com › docs › RemovingSignalHandler.html
Removing a Signal Handler - NV5 Geospatial Software
View our Documentation Center document now and explore other helpful examples for using IDL, ENVI and other products.
🌐
Tutorialspoint
tutorialspoint.com › python › python_signal_handling.htm
Python - Signal Handling
Signal handling in Python allows you to define custom handlers for managing asynchronous events such as interrupts or termination requests from keyboard, alarms, and even system signals. You can control how your program responds to various signals by defining custom handlers.
Find elsewhere
🌐
The Ramblings
anonbadger.wordpress.com › 2018 › 12 › 15 › python-signal-handlers-and-exceptions
Python, signal handlers, and exceptions – The Ramblings
December 19, 2018 - If you search the internet for how to implement a timeout in Python, you’ll find tons of examples using signals, including one from the standard library documentation and one which is probably where the design for our original decorator came from. So let’s create some quick test code to show how the signal works to implement a timeout · import signal import time def handler(signum, frame): print('Signal handler called with signal', signum) raise OSError("timeout exceeded!") def long_function(): time.sleep(10) # Set the signal handler and a 1-second alarm old_handler = signal.signal(signal.SIGALRM, handler) signal.alarm(1) # This sleeps for longer than the alarm start = time.time() try: long_function() except OSError as e: duration = time.time() - start print('Duration: %.2f' % duration) raise finally: signal.signal(signal.SIGALRM, old_handler) signal.alarm(0) # Disable the alarm
🌐
Readthedocs
cement.readthedocs.io › en › 2.10 › dev › signal_handling
Signal Handling — Cement 2.10.12 documentation
Python provides the Signal library allowing developers to catch Unix signals and set handlers for asynchronous events. For example, the ‘SIGTERM’ (Terminate) signal is received when issuing a ‘kill’ command for a given Unix process. Via the signal library, we can set a handler (function) callback that will be executed when that signal is received.
🌐
Python
docs.python.org › 3.9 › library › signal.html
signal — Set handlers for asynchronous events — Python 3.9.24 documentation
The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
🌐
GitHub
github.com › python › cpython › issues › 111400
Improved signal handling initialization for embedded Python · Issue #111400 · python/cpython
October 27, 2023 - If someone calls import signal later on, then the user has shown clear intent to diverge from the initial state of skipping signal handling setup in Python. However, the problem is that while I can avoid calling import signal in this embedded environment, it is hard to avoid it as a transitive dependency. For example, the library asyncio calls import signal. Of course, I can change my signal handler from SIG_DFL to anything else.
Author   whitevegagabriel
🌐
GitHub
github.com › python › cpython › issues › 88378
asyncio overrides signal handlers · Issue #88378 · python/cpython
May 22, 2021 - activity = <Date 2021-05-22.04:05:46.824> actor = 'Francisco Demartino' assignee = 'none' closed = False closed_date = None closer = None components = ['asyncio'] creation = <Date 2021-05-22.03:09:03.053> creator = 'Francisco Demartino' dependencies = [] files = [] hgrepos = [] issue_num = 44212 keywords = ['patch'] message_count = 2.0 messages = ['394174', '394176'] nosy_count = 3.0 nosy_names = ['asvetlov', 'yselivanov', 'Francisco Demartino'] pr_nums = ['26306'] priority = 'normal' resolution = None stage = 'patch review' status = 'open' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue44212' versions = ['Python 3.6', 'Python 3.7', 'Python 3.8', 'Python 3.9', 'Python 3.10', 'Python 3.11']
Published   May 22, 2021
Author   franciscod
🌐
Python
docs.python.org › 3.10 › library › signal.html
signal — Set handlers for asynchronous events — Python 3.10.20 documentation
The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
🌐
Python
docs.python.org › 3.4 › library › signal.html
18.8. signal — Set handlers for asynchronous events — Python 3.4.10 documentation
June 16, 2019 - The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
🌐
Read the Docs
stackless.readthedocs.io › en › 3.7-slp › library › signal.html
signal — Set handlers for asynchronous events — Stackless-Python 3.7.9 documentation
The function accepts the signal and removes it from the pending list of signals. If one of the signals in sigset is already pending for the calling thread, the function will return immediately with information about that signal. The signal handler is not called for the delivered signal.
🌐
GitHub
github.com › python › cpython › blob › main › Lib › unittest › signals.py
cpython/Lib/unittest/signals.py at main · python/cpython
removeHandler() try: return method(*args, **kwargs) finally: signal.signal(signal.SIGINT, initial) return inner · · global _interrupt_handler ·
Author   python