深入分析在Python模块顶层运行的代码引起的一个Bug


Posted in Python onJuly 04, 2014

然后我们在Interactive Python prompt中测试了一下:

>>> import subprocess
  >>> subprocess.check_call("false")
  0

而在其他机器运行相同的代码时, 却正确的抛出了错误:

>>> subprocess.check_call("false")
  Traceback (most recent call last):
   File "", line 1, in 
   File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 542, in check_call
    raise CalledProcessError(retcode, cmd)
  subprocess.CalledProcessError: Command 'false' returned non-zero exit status 1

看来是subprecess误以为子进程成功的退出了导致的原因.

深入分析

第一眼看上去, 这一问题应该是Python自身或操作系统引起的. 这到底是怎么发生的? 于是我的同事查看了subprocess的wait()方法:

def wait(self):
  """Wait for child process to terminate. Returns returncode attribute."""
  while self.returncode is None:
   try:
    pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
   except OSError as e:
    if e.errno != errno.ECHILD:
     raise
    # This happens if SIGCLD is set to be ignored or waiting
    # for child processes has otherwise been disabled for our
    # process. This child is dead, we can't get the status.
    pid = self.pid
    sts = 0
   # Check the pid and loop as waitpid has been known to return
   # 0 even without WNOHANG in odd situations. issue14396.
   if pid == self.pid:
    self._handle_exitstatus(sts)
  return self.returncode

可见, 如果os.waitpid的ECHILD检测失败, 那么错误就不会被抛出. 通常, 当一个进程结束后, 系统会继续记录其信息, 直到母进程调用wait()方法. 在此期间, 这一进程就叫"zombie". 如果子进程不存在, 那么我们就无法得知其是否成功还是失败了.

以上代码还能解决另外一个问题: Python默认认为子进程成功退出. 大多数情况下, 这一假设是没问题的. 但当一个进程明确表明忽略子进程的SIGCHLD时, waitpid()将永远是成功的.

回到原来的代码中

我们是不是在我们的程序中明确设置忽略SIGCHLD? 不太可能, 因为我们使用了大量的子进程, 但只有极少数情况下才出现同样的问题. 再使用git grep后, 我们发现只有在一段独立代码中, 我们忽略了SIGCHLD. 但这一代吗根本就不是程序的一部分, 只是引用了一下.

一星期后

一星期后, 这一错误又再一次发生. 并且通过简单的调试, 在debugger中重现了该错误.

经过一些测试, 我们确定了正是由于程序忽略了SIGCHLD才引起的这一bug. 但这是怎么发生的呢?

我们查看了那段独立代码, 其中有一段:

signal.signal(signal.SIGCHLD, signal.SIG_IGN)
我们是不是无意间import了这段代码到程序中? 结果显示我们的猜测是正确的. 当import了这段代码后, 由于以上语句是在这一module的顶层, 而不是在一个function中, 导致了它的运行, 忽略了SIGCHLD, 从而导致了子进程错误没有被抛出!

总结

这一bug的发生, 给了我们两个教训. 第一是, 在debug检查时, 应该从新的代码到老的代码, 再到Python Library. 因为新代码发生错误的几率大于老代码, 而python library中发生错误的几率更小.

第二是, 不要将可能会引起副作用的代码写在module顶层, 而应当写到functuon中. 因为如果该module被import, 那么在顶层的代码就会运行, 导致各种不可知的事件发生.

Python 相关文章推荐
进一步探究Python的装饰器的运用
May 05 Python
Python功能键的读取方法
May 28 Python
Python实现中一次读取多个值的方法
Apr 22 Python
mac下给python3安装requests库和scrapy库的实例
Jun 13 Python
python实时监控cpu小工具
Jun 21 Python
对Python中内置异常层次结构详解
Oct 18 Python
PyQt5实现简单数据标注工具
Mar 18 Python
详解Python 定时框架 Apscheduler原理及安装过程
Jun 14 Python
简单了解python关键字global nonlocal区别
Sep 21 Python
用python实现一个简单计算器(完整DEMO)
Oct 14 Python
详解非极大值抑制算法之Python实现
Jun 28 Python
python pygame 开发五子棋双人对弈
May 02 Python
python之import机制详解
Jul 03 #Python
Python之eval()函数危险性浅析
Jul 03 #Python
python的绘图工具matplotlib使用实例
Jul 03 #Python
python绘图库Matplotlib的安装
Jul 03 #Python
Python实现全局变量的两个解决方法
Jul 03 #Python
Python实现端口复用实例代码
Jul 03 #Python
在 Django/Flask 开发服务器上使用 HTTPS
Jul 03 #Python
You might like
MySQL修改密码方法总结
2008/03/25 PHP
ThinkPHP3.1新特性之内容解析输出详解
2014/06/19 PHP
PHP中的Streams详细介绍
2014/11/12 PHP
php微信开发之谷歌测距
2018/06/14 PHP
Discuz不使用插件实现简单的打赏功能
2019/03/21 PHP
php实现记事本案例
2020/10/20 PHP
学习YUI.Ext 第七天--关于View&JSONView
2007/03/10 Javascript
javascript 短路法代码精简
2009/08/20 Javascript
通过身份证号得到出生日期和性别的js代码
2009/11/23 Javascript
奉献给JavaScript初学者的编写开发的七个细节
2011/01/11 Javascript
javascript跑马灯悬停放大效果实现代码
2012/12/12 Javascript
JS解决ie6下png透明的方法实例
2013/08/02 Javascript
浅谈JavaScript Array对象
2014/12/29 Javascript
基于jQuery实现在线选座之高铁版
2015/08/24 Javascript
浅谈JavaScript find 方法不支持IE的问题
2017/09/28 Javascript
vue 实现 ios 原生picker 效果及实现思路解析
2017/12/06 Javascript
使用element-ui table expand展开行实现手风琴效果
2019/03/15 Javascript
ionic4+angular7+cordova上传图片功能的实例代码
2019/06/19 Javascript
微信小程序基于movable-view实现滑动删除效果
2020/01/08 Javascript
解决python2.7 查询mysql时出现中文乱码
2016/10/09 Python
用python做一个搜索引擎(Pylucene)的实例代码
2017/07/05 Python
JS设计模式之责任链模式实例详解
2018/02/03 Python
python模块之subprocess模块级方法的使用
2019/03/26 Python
Python+OpenCV实现实时眼动追踪的示例代码
2019/11/11 Python
在PyCharm中遇到pip安装 失败问题及解决方案(pip失效时的解决方案)
2020/03/10 Python
Numpy 理解ndarray对象的示例代码
2020/04/03 Python
解决Jupyter Notebook开始菜单栏Anaconda下消失的问题
2020/04/13 Python
css3media响应式布局实例
2016/07/08 HTML / CSS
学校门卫工作职责
2013/12/07 职场文书
会计与出纳自荐书范文
2014/03/16 职场文书
房屋公证委托书
2014/04/03 职场文书
大学生标准自荐书
2014/06/15 职场文书
工地宣传标语
2014/06/18 职场文书
公司表扬稿范文
2015/05/05 职场文书
描述鲁迅的名言整理,一生受用
2019/08/08 职场文书
浅谈Python实现opencv之图片色素的数值运算和逻辑运算
2021/06/23 Python