深入分析在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下的twisted框架入门指引
Apr 15 Python
Python中自定义函数的教程
Apr 27 Python
Django中login_required装饰器的深入介绍
Nov 24 Python
Selenium定时刷新网页的实现代码
Oct 31 Python
Python数据分析:手把手教你用Pandas生成可视化图表的教程
Dec 15 Python
对Python3中dict.keys()转换成list类型的方法详解
Feb 03 Python
python实现AES和RSA加解密的方法
Mar 28 Python
python常用排序算法的实现代码
Nov 08 Python
python梯度下降算法的实现
Feb 24 Python
python使用gdal对shp读取,新建和更新的实例
Mar 10 Python
Python开发之身份证验证库id_validator验证身份证号合法性及根据身份证号返回住址年龄等信息
Mar 20 Python
Python基础之操作MySQL数据库
May 06 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
【星际争霸1】人族1v7家ZBath
2020/03/04 星际争霸
让PHP支持页面回退的两种方法[转]
2007/02/14 PHP
DISCUZ 论坛管理员密码忘记的解决方法
2009/05/14 PHP
php header Content-Type类型小结
2011/07/03 PHP
php支付宝接口用法分析
2015/01/04 PHP
PHP的引用详解
2015/02/22 PHP
php自定义函数实现二维数组按指定key排序的方法
2016/09/29 PHP
php基于dom实现读取图书xml格式数据的方法
2017/02/03 PHP
jquery js 重置表单 reset()具体实现代码
2013/08/05 Javascript
js模仿java的Map集合详解
2016/01/06 Javascript
JS定时器使用,定时定点,固定时刻,循环执行详解
2016/05/31 Javascript
js微信扫描二维码登录网站技术原理
2016/12/01 Javascript
详谈$.data()的用法和作用
2017/02/13 Javascript
Mac系统下Webstorm快捷键整理大全
2017/05/28 Javascript
JS沙箱模式实例分析
2017/09/04 Javascript
JS实现移动端在线签协议功能
2019/08/22 Javascript
如何在node环境实现“get数据解析”代码实例
2020/07/03 Javascript
Vue项目打包部署到apache服务器的方法步骤
2021/02/01 Vue.js
web.py在模板中输出美元符号的方法
2014/08/26 Python
python psutil库安装教程
2018/03/19 Python
Numpy数组的保存与读取方法
2018/04/04 Python
Python连接Mssql基础教程之Python库pymssql
2018/09/16 Python
python调用webservice接口的实现
2019/07/12 Python
使用OpenCV实现仿射变换—旋转功能
2019/08/29 Python
tensorflow之并行读入数据详解
2020/02/05 Python
欧洲最大的笔和书写专家:The Pen Shop
2017/03/19 全球购物
JBL美国官方商店:扬声器、耳机等
2019/12/01 全球购物
Oracle中delete,truncate和drop的区别
2016/05/05 面试题
学术会议欢迎词
2014/01/09 职场文书
法律进机关实施方案
2014/03/12 职场文书
学生个人自我鉴定
2014/03/26 职场文书
认错检讨书
2014/10/02 职场文书
教师党员承诺书2015
2015/01/21 职场文书
零基础学java之方法的定义与调用详解
2022/04/10 Java/Android
windows server2012 R2下安装PaddleOCR服务的的详细步骤
2022/09/23 Servers
Elasticsearch6.2服务器升配后的bug(避坑指南)
2022/09/23 Servers