How to explain the reentrant RuntimeError caused by printing in signal handlers?
You might prefer to inhibit delivery of SIGINT to the child, so there's no race,perhaps by putting it in a different process group,or by having it ignore the signal.Then only SIGTERM from the parent would matter.
To reveal where it was interrupted, use this:
sig_num, frame = args print(dis.dis(frame.f_code.co_code)) print(frame.f_lasti)
The bytecode offsets in the left margin correspond tothat last instruction executed offset.
Other items of interest includeframe.f_lineno
,frame.f_code.co_filename
, andframe.f_code.co_names
.
This issue becomes moot in python 3.7.3, which no longer exhibits the symptom.
Signals are processed between opscode(see eval_frame_handle_pending()in python's opscode processor loop), but not limited to it. print
is a perfect example. It is implemented based on _io_BufferedWriter_write_impl(), which has a structure like
ENTER_BUFFERED()
=> it locks buffer
PyErr_CheckSignals()
=> it invoke signal handler
LEAVE_BUFFERED()
=> it unlocks buffer
by calling PyErr_CheckSignals()
, it invoke another signal handler, which has another print
in this case. The 2nd print
run ENTER_BUFFERED()
again, because the buffer is already locked by previous print
in 1st signal handler, so the reentrant
exception is thrown as below snippet shows.
// snippet of ENTER_BUFFERED static int _enter_buffered_busy(buffered *self) { int relax_locking; PyLockStatus st; if (self->owner == PyThread_get_thread_ident()) { PyErr_Format(PyExc_RuntimeError, "reentrant call inside %R", self); return 0; } } #define ENTER_BUFFERED(self) \ ( (PyThread_acquire_lock(self->lock, 0) ? \ 1 : _enter_buffered_busy(self)) \ && (self->owner = PyThread_get_thread_ident(), 1) )
P.S.
Reentrant Functions from Advanced Programming in the Unix Environment.
The Single UNIX Specification specifies the functions that are guaranteed to be safe to call from within a signal handler. These functions are reentrant and are called async-signal safe. Most of the functions that are not reentrant because
- they are known to use static data structures,
- they call malloc or free
- they are part of the standard I/O library. Most implementations of the standard I/O library use global data structures in a nonreentrant way.
print
in Python belongs to this category.