Default handler for SIGINT raises KeyboardInterrupt. However, if a program is inside a __del__ method (because of an ongoing garbage collection), the exception is ignored with the following message printed to stderr:
Exception KeyboardInterrupt in <...> ignored
As a result, the program continues to work despite receiving SIGINT. Of course, I can define my own handler for SIGINT that sets a global variable sigint_received to True, and then often check the value of the variable in my program. But this looks ugly.
Is there an elegant and reliable way to make sure that the python program gets interrupted after receiving SIGINT?
Before I dive into my solution, I want to highlight the scary red "Warning:" sidebar in the docs for object.__del__
(emphasis mine):
Due to the precarious circumstances under which
__del__()
methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed tosys.stderr
instead. [...]__del__()
methods should do the absolute minimum needed to maintain external invariants.
This suggests to me that any __del__
method that's at serious risk of being interrupted by an interactive user's Ctrl-C
might be doing too much. So my first suggestion would be to look for ways to minimize your __del__
method, whatever it is.
Or to put it another way: If your __del__
method really does do "the absolute minimum needed", then how can it be safe to kill the process half-way through?
The only solution I could find was indeed a custom signal handler for signal.SIGINT
... but a lot of the obvious tricks didn't work:
sys.exit
Calling sys.exit
from the signal handler just raised a SystemExit
exception, which was ignored. Python's C API docs suggest that it is impossible for the Python interpreter to raise any exception during a __del__
method:
void
PyErr_WriteUnraisable
(PyObject *obj)
[Called when...] it is impossible for the interpreter to actually raise the exception [...] for example, when an exception occurs in an
__del__()
method.
Your idea of setting a global "drop dead" variable inside the signal handler worked only partially --- although it updated the variable, nothing got a chance to read that variable until after the __del__
method returned. So for several seconds, the Ctrl-C
appeared to have done nothing.
This might be good enough if you just want to terminate the process "eventually", since it will exit whenever the __del__
method returns. But since you probably want to shut down the process without waiting (both SIGINT
and KeyboardInterrupt
typically come from an impatient user), this won't do.
os.kill
Since I couldn't find a way to convince the Python interpreter to kill itself, my solution was to have the (much more persuasive) operating system do it for me. This signal handler uses os.kill
to send a stronger SIGTERM
to its own process ID, causing the Python interpreter itself to exit.
def _sigterm_this_process(signum, frame):
pid = os.getpid()
os.kill(pid, signal.SIGTERM)
return
# Elsewhere...
signal.signal(signal.SIGINT, _sigterm_this_process)
Once the custom signal handler was set, Ctrl-C
caused the __del__
method (and the entire program) to exit immediately.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With