![](/img/trans.png)
[英]RuntimeError(“populate() isn't reentrant”) on elastic beanstalk
[英]How to explain the reentrant RuntimeError caused by printing in signal handlers?
代码:
# 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
只需执行caller.py
然后按Ctrl+C
,程序就会随机引发RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>
。 从文档中我了解到信号处理程序是异步调用的,在这种情况下,两个信号 SIGINT( Ctrl+C
action) 和 SIGTERM( p.terminate()
) 几乎同时发送,导致竞争条件。
但是,从这篇文章中我了解到signal
模块不会在低级 (C) 处理程序中执行信号处理程序。 相反,它设置一个标志,解释器检查字节码指令之间的标志,然后调用 python 信号处理程序。 换句话说,虽然信号处理程序可能会弄乱主线程中的控制流,但字节码指令始终是原子的。
这似乎与我的示例程序的结果相矛盾。 就我而言, print
和隐式_io.BufferedWriter
都是用纯 C 实现的,因此调用print
函数应该只消耗一个字节码指令( CALL_FUNCTION
)。 我很困惑:在一个线程上的一条不间断指令中,函数如何可重入?
我正在使用 Python 3.6.2。
您可能更喜欢禁止将 SIGINT 传递给孩子,因此没有竞争,也许是通过将它放在不同的进程组中,或者让它忽略信号。 那么只有来自父级的 SIGTERM 才重要。
要显示中断的位置,请使用以下命令:
sig_num, frame = args
print(dis.dis(frame.f_code.co_code))
print(frame.f_lasti)
左边距中的字节码偏移量对应于最后一条指令执行的偏移量。
其他感兴趣的项目包括frame.f_lineno
、 frame.f_code.co_filename
和frame.f_code.co_names
。
这个问题在 python 3.7.3 中变得没有实际意义,它不再出现这种症状。
信号在 opscode 之间进行处理(参见 python 的 opscode 处理器循环中的eval_frame_handle_pending() ),但不限于此。 print
就是一个完美的例子。 它是基于_io_BufferedWriter_write_impl() 实现的,其结构类似于
ENTER_BUFFERED()
=> 它锁定缓冲区
PyErr_CheckSignals()
=> 它调用信号处理程序
LEAVE_BUFFERED()
=> 解锁缓冲区
通过调用PyErr_CheckSignals()
,它调用另一个信号处理程序,在这种情况下它有另一个print
。 第二次print
再次运行ENTER_BUFFERED()
,因为缓冲区已经被第一次信号处理程序中的先前print
锁定,因此抛出reentrant
异常,如下代码段所示。
// 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) )
聚苯乙烯
Unix 环境中高级编程的可重入函数。
单一 UNIX 规范指定了可以保证从信号处理程序中安全调用的函数。 这些函数是可重入的,称为异步信号安全。 大多数不可重入的函数是因为
print
属于这一类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.