Code:
# callee.py
import signal
import sys
import time
def int_handler(*args):
for i in range(10):
print('INTERRUPT', args)
sys.exit()
if __name__ == '__main__':
signal.signal(signal.SIGINT, int_handler)
signal.signal(signal.SIGTERM, int_handler)
while 1:
time.sleep(1)
# caller.py
import subprocess
import sys
def wait_and_communicate(p):
out, err = p.communicate(timeout=1)
print('========out==========')
print(out.decode() if out else '')
print('========err==========')
print(err.decode() if err else '')
print('=====================')
if __name__ == '__main__':
p = subprocess.Popen(
['/usr/local/bin/python3', 'callee.py'],
stdout=sys.stdout,
stderr=subprocess.PIPE,
)
while 1:
try:
wait_and_communicate(p)
except KeyboardInterrupt:
p.terminate()
wait_and_communicate(p)
break
except subprocess.TimeoutExpired:
continue
Simply execute caller.py
and then press Ctrl+C
, the program will raise RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>
randomly. From the documentation I learn that signal handlers are called asynchronously, and in this case two signals SIGINT(Ctrl+C
action) and SIGTERM(p.terminate()
) are sent nearly at the same time, causing a race condition.
However, from this post I learn that signal
module doesn't execute signal handler inside low-level (C) handler. Instead, it sets a flag, and the interpreter checks the flag between bytecode instructions and then invokes the python signal handler. In other words, while signal handlers may mess up the control flow in the main thread, a bytecode instruction is always atomic.
This seems to contradict with the result of my example program. As far as I am concerned, print
and the implicit _io.BufferedWriter
are both implemented in pure C, and thus calling print
function should consume only one bytecode instruction (CALL_FUNCTION
). I am confused: within one uninterrupted instruction on one thread, how can a function be reentrant?
I'm using Python 3.6.2.