51CTO诚邀您9月23号和秒拍/国美/美团元专家一路聊智能CDN的优化之路,抓紧时光哦!
写在前面
前几天工作时碰到了一个匪夷所思的问题。经由几回测验测验后问题得以解决,但问题产生的原因却仍令人费解。查找 SO 无不雅,我决定翻看 Python 的源码。断断续续地研究了几天,终于恍然大年夜悟。撰此文以记。
本文情况:
- Ubuntu 16.04 (64 bit)
- Python 3.6.2
应用的 C 源码可以大年夜 Python 官网 获取。
原由
工作时用到了 celery 作为异步义务队列,为便利调试,我写了一个脚本用以启动/封闭 celery 主过程。代码简化后如下:
代码启动了 celery worker,尝尝试在捕获到 KeyboardInterrupt 异常时将其热封闭。
初看上去没什愦问题。然而实际测试时却产生了十分诡异的工作:按下 Ctrl+C 后,法度榜样 有时 会抛出如许的异常:RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>’>。诡异之处有两点:
这个结不雅大年夜大年夜出乎了我的料想。随机性异常是浩瀚最难缠的问题之一,因为这常平平易近味着并发问题,涉及底层常识,病灶隐蔽,调试难度大年夜,同时没有有效的手段断定问题是否彻调果断(可能只是降低了频率)。
异常信息中有两个词很关键:reentrant 和 stdout。reentrant call 解释有一个弗成重入的函数被递归调用了;stdout 则指清楚明了产生的地点和机会。初步可以剖断:因为某种原因,有两股控制流在同时操控 stdout。
“可重入”是什么?根据 Wikipedia 的定义:如不雅一个子法度榜样能在履行时被中断并在之后被精确地、安然地唤起,它就被称为可重入的。依附于全局数据的过程是弗成重入的,如 printf(依附于全局文件描述符)、malloc(依附与和堆相干的一系列数据构造)等函数。须要留意的是,可重人道(reentrant)与 线程安然性(thread-safe)并不等价,甚至不存在包含关系,Wikipedia 中给出了相干的反例。
多次测验测验后,出现了一条线索:有时刻 worker: Warm shutdown (MainProcess) 这个字符串会被二次打印,机会不肯定。这句话是 celery 将要热封闭时的提示语,二次出现只可能是主过程收到了第二个旌旗灯号。浏览 celery 的文档 可知,SIGINT 和 SIGTERM 旌旗灯号可以激发烧封闭。回头浏览我的代码,个中只有一处发送了 SIGTERM 旌旗灯号(celery_process.terminate()),至于另一个神秘的旌旗灯号,我困惑是 SIGINT。
SO 一下,结不雅印证了我的猜想:
If you are generating the SIGINT with Ctrl-C>
猜测
UNIX 旌旗灯号处理是一个相当奇葩的过程——当过程收到一个旌旗灯号时,内核会选择一条线程(以必定的规矩),中断其当前控制流,将控制流强行转给旌旗灯号处理函数,待其履行完毕后再将控制流交还给原线程。时序图如下:
异常的 traceback 指向 celery 包,也就是嗣魅这是在 celery 主过程内部产生的异常
因为控制流转换产生在同一条线程上,很多线程间同步机制会掉效甚至报错。是以旌旗灯号处理函数的编写要比线程函数加倍严格,对同一个文件输出是被禁止并且无解的,因为很可能会产生如许的工作:
并且这个问题不克不及经由过程加锁来解决(因为是在同一个线程中,会逝世锁)。
是以,我猜测异常产生时的事宜时序是如许的:在 print 未履行完时中断,又在旌旗灯号处理函数中调用 print,触发了重入检测,引起 RuntimeError:
推荐阅读
CIO半月刊第十九期|李克强总理再提制造业转型,51CTO带你摸底制造业
51CTO诚邀您9月23号和秒拍/国美/美团元专家一路聊智能CDN的优化之路,抓紧时光哦! 【编辑推荐】CIO半月刊第十四期|查访过30多位CIO,我们得出如许的结论CIO半月刊第十五期|某有名旅游企业>>>详细阅读
本文标题:一个Reentrant Error引发的对Python信号机制的探索和思考
地址:http://www.17bianji.com/lsqh/37408.html
1/2 1