[Python-ideas] Responsive signal handling
Devin Jeanpierre
jeanpierreda at gmail.com
Mon Jun 22 04:16:32 CEST 2015
On the topic of obscure concurrency trivia, signal handling in Python
is not very friendly, and I'm wondering if anyone is interested in
reviewing changes to fix it.
The world today: handlers only run in the main thread, but if the main
thread is running a bytecode (e.g. a call to a C function), it will
wait for that first. For example, signal handlers don't get run if you
are in the middle of a lock acquisition, thread join, or (sometimes) a
select call, until after the call returns (which may take a very long
time).
This makes it difficult to be responsive to signals without
acrobatics, or without using a library that does those acrobatics for
you (such as Twisted.) Being responsive to SIGTERM and SIGINT is, IMO,
important for running programs in the cloud, since otherwise they may
be forcefully killed by the job manager, causing user-facing errors.
(It's also annoying as a command line user when you can't kill a
process with anything less than SIGKILL.)
I would stress that, by and large, the signal module is a trap that
most people get wrong, and I don't think the stdlib solution should be
the way it is. (e.g. it looks to me like gunicorn gets it wrong, and
certainly asyncio did in early versions.)
One could implement one or both of the following:
- Keep only running signal handlers in the main thread, but allow them
to run even in the middle of a call to a C function for as many C
functions as we can.
This is not possible in general, but it can be made to work for all
blocking operations in the stdlib. Operations that run in C but just
take a long time, or that are part of third-party code, will continue
to inhibit responsiveness.
- Run signal handlers in a dedicated separate thread.
IMO this is generally better than running signal handlers in the main
thread, because it eliminates the separate concept of "async-safe" and
just requires "thread-safe". So you can use regular threading
synchronization primitives for safety, instead of relying on luck /
memorized lists of atomic/re-entrant operations.
Something still needs to run in the main thread though, for e.g.
KeyboardInterrupt, so this is not super straightforward. Also, it
could break any code that really relies on signal handlers running in
the main thread.
Either approach can be turned into a library, albeit potentially
hackily in the first case.
-- Devin
More information about the Python-ideas
mailing list