深入分析在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的print用法示例
Feb 11 Python
python基础教程之常用运算符
Aug 29 Python
python类的继承实例详解
Mar 30 Python
Python中进程和线程的区别详解
Oct 29 Python
教你用Python创建微信聊天机器人
Mar 31 Python
python基础教程项目四之新闻聚合
Apr 02 Python
python将txt等文件中的数据读为numpy数组的方法
Dec 22 Python
python写程序统计词频的方法
Jul 29 Python
python选取特定列 pandas iloc,loc,icol的使用详解(列切片及行切片)
Aug 06 Python
使用python处理题库表格并转化为word形式的实现
Apr 14 Python
三步解决python PermissionError: [WinError 5]拒绝访问的情况
Apr 22 Python
python实现暗通道去雾算法的示例
Sep 27 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
哪吒敖丙传:新人物二哥敖乙出场 小敖丙奶气十足
2020/03/08 国漫
应用开发中涉及到的css和php笔记分享
2011/08/02 PHP
php使用filter过滤器验证邮箱 ipv6地址 url验证
2013/12/25 PHP
PHP使用mkdir创建多级目录的方法
2015/12/22 PHP
php与python实现的线程池多线程爬虫功能示例
2016/10/12 PHP
PHPMailer发送邮件
2016/12/28 PHP
PHP实现mysqli批量执行多条语句的方法示例
2017/07/22 PHP
分析php://output和php://stdout的区别
2018/05/06 PHP
关于laravel模板中生成URL的几种模式总结
2019/10/18 PHP
php多进程中的阻塞与非阻塞操作实例分析
2020/03/04 PHP
使Ext的Template可以解析二层的json数据的方法
2007/12/22 Javascript
自己写的Javascript计算时间差函数
2013/10/28 Javascript
JavaScript控制图片加载完成后调用回调函数的方法
2015/03/20 Javascript
工厂模式在JS中的实践
2017/01/18 Javascript
拖动时防止选中
2017/02/03 Javascript
vue实现简单表格组件实例详解
2017/04/16 Javascript
vue省市区三联动下拉选择组件的实现
2017/04/28 Javascript
JavaScript模块详解
2017/12/18 Javascript
微信小程序中使用wxss加载图片并实现动画效果
2018/08/13 Javascript
vue实现element-ui对话框可拖拽功能
2018/08/17 Javascript
vue .js绑定checkbox并获取、改变选中状态的实例
2018/08/24 Javascript
详解关于微信setData回调函数中的坑
2019/02/18 Javascript
vue中created和mounted的区别浅析
2019/08/13 Javascript
javascript实现文字跑马灯效果
2020/06/18 Javascript
python+selenium开发环境搭建图文教程
2017/08/11 Python
Python3开发实例之非关系型图数据库Neo4j安装方法及Python3连接操作Neo4j方法实例
2020/03/18 Python
解决pycharm不能自动保存在远程linux中的问题
2021/02/06 Python
html5如何在Canvas中实现自定义路径动画示例
2017/09/18 HTML / CSS
网站性能延迟加载图像的五种技巧(小结)
2020/08/13 HTML / CSS
乌克兰网上服装店:Bolf.ua
2018/10/30 全球购物
石油工程专业毕业生求职信
2014/04/13 职场文书
大队干部竞选演讲稿
2014/04/28 职场文书
如何写观后感
2015/06/19 职场文书
观后感的写法
2015/06/19 职场文书
公司员工培训管理制度
2015/08/04 职场文书
Html5同时支持多端sdk的小技巧
2021/11/17 HTML / CSS