深入分析在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 相关文章推荐
Python3中条件控制、循环与函数的简易教程
Nov 21 Python
Python查看微信撤回消息代码
Jun 07 Python
CentOS 7下安装Python3.6 及遇到的问题小结
Nov 08 Python
python利用百度AI实现文字识别功能
Nov 27 Python
python增加图像对比度的方法
Jul 12 Python
python监控nginx端口和进程状态
Sep 06 Python
Python udp网络程序实现发送、接收数据功能示例
Dec 09 Python
python3读取csv文件任意行列代码实例
Jan 13 Python
python判断链表是否有环的实例代码
Jan 31 Python
Eclipse配置python默认头过程图解
Apr 26 Python
Python如何使用PIL Image制作GIF图片
May 16 Python
Python接口测试文件上传实例解析
May 22 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
PHP集成FCK的函数代码
2008/09/27 PHP
php过滤html标记属性类用法实例
2014/09/23 PHP
php获取字符串中各个字符出现次数的方法
2015/02/23 PHP
PHP 网站修改默认访问文件的nginx配置
2017/05/27 PHP
JS 统计时间
2021/03/09 Javascript
jQuery的实现原理的模拟代码 -1 核心部分
2010/08/01 Javascript
JavaScript中关于indexOf的使用方法与问题小结
2010/08/05 Javascript
JavaScript继承方式实例
2010/10/29 Javascript
在js(jquery)中获得文本框焦点和失去焦点的方法
2012/12/04 Javascript
checkbox设置复选框的只读效果不让用户勾选
2013/08/12 Javascript
js函数模拟显示桌面.scf程序示例
2014/04/20 Javascript
使用documentElement正确取得当前可见区域的大小
2014/07/25 Javascript
JS获取及设置TextArea或input文本框选择文本位置的方法
2015/03/24 Javascript
浅谈javascript中基本包装类型
2015/06/03 Javascript
javascript禁止访客复制网页内容的实现代码
2015/08/05 Javascript
javascript日期验证之输入日期大于等于当前日期
2015/12/13 Javascript
JavaScript实现ASC转汉字及汉字转ASC的方法
2016/01/23 Javascript
解决bootstrap导航栏navbar在IE8上存在缺陷的方法
2016/07/01 Javascript
两行代码轻松搞定JavaScript日期验证
2016/08/03 Javascript
纯javaScript、jQuery实现个性化图片轮播【推荐】
2017/01/08 Javascript
树结构之JavaScript
2017/01/24 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
2018/01/12 Javascript
JavaScript同源策略和跨域访问实例详解
2018/04/03 Javascript
解决vue打包后vendor.js文件过大问题
2019/07/03 Javascript
原生JS实现萤火虫效果
2020/03/07 Javascript
[01:19:46]EG vs Secret 2019国际邀请赛淘汰赛 胜者组 BO3 第二场 8.21.mp4
2020/07/19 DOTA
总结python实现父类调用两种方法的不同
2017/01/15 Python
详解python单元测试框架unittest
2018/07/02 Python
解决keras加入lambda层时shape的问题
2020/06/11 Python
python从Oracle读取数据生成图表
2020/10/14 Python
前台接待岗位职责
2013/12/03 职场文书
黄河象教学反思
2014/02/10 职场文书
《纸船和风筝》教学反思
2014/02/15 职场文书
大学生助学金感谢信
2015/01/21 职场文书
2016党性教育学习心得体会
2016/01/21 职场文书
Jpa Specification如何实现and和or同时使用查询
2021/11/23 Java/Android