深入分析在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之模拟鼠标键盘动作具体实现
Dec 30 Python
Python创建日历实例
Aug 21 Python
Python利用flask sqlalchemy实现分页效果
Aug 02 Python
Python 多进程和数据传递的理解
Oct 09 Python
一道python走迷宫算法题
Jan 22 Python
使用Anaconda3建立虚拟独立的python2.7环境方法
Jun 11 Python
opencv python 图像轮廓/检测轮廓/绘制轮廓的方法
Jul 03 Python
Python函数装饰器原理与用法详解
Aug 16 Python
pygame库实现俄罗斯方块小游戏
Oct 29 Python
Python使用Chrome插件实现爬虫过程图解
Jun 09 Python
Python脚本实现Zabbix多行日志监控过程解析
Aug 26 Python
基于注解实现 SpringBoot 接口防刷的方法
Mar 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
php include,include_once,require,require_once
2008/09/05 PHP
实用的简单PHP分页集合包括使用方法
2013/10/21 PHP
Windows中使用计划任务自动执行PHP程序实例
2014/05/09 PHP
prototype1.4中文手册
2006/09/22 Javascript
Prototype最新版(1.5 rc2)使用指南(1)
2007/01/10 Javascript
jQuery怎么解析Json字符串(Json格式/Json对象)
2013/08/09 Javascript
常见浏览器多长时间会提示“脚本运行时间过长”总结
2014/04/29 Javascript
node.js中的定时器nextTick()和setImmediate()区别分析
2014/11/26 Javascript
关于JavaScript和jQuery的类型判断详解
2016/10/08 Javascript
vue中将html字符串转换成html后遇到的问题小结
2018/12/10 Javascript
iview form清除校验状态的实现
2019/09/19 Javascript
微信小程序实现菜单左右联动
2020/05/19 Javascript
解决vue项目router切换太慢问题
2020/07/19 Javascript
[03:07]完美世界DOTA2联赛PWL DAY10 决赛集锦
2020/11/11 DOTA
用Python脚本来删除指定容量以上的文件的教程
2015/05/04 Python
python实现对指定输入的字符串逆序输出的6种方法
2018/04/26 Python
python抽取指定url页面的title方法
2018/05/11 Python
对python抓取需要登录网站数据的方法详解
2018/05/21 Python
Pandas GroupBy对象 索引与迭代方法
2018/11/16 Python
Python3使用TCP编写一个简易的文件下载器功能
2019/05/08 Python
pandas中的series数据类型详解
2019/07/06 Python
PyTorch中反卷积的用法详解
2019/12/30 Python
python实现双色球随机选号
2020/01/01 Python
如何基于Python + requests实现发送HTTP请求
2020/01/13 Python
Python中itertools的用法详解
2020/02/07 Python
Python中有几个关键字
2020/06/04 Python
python如何发送带有附件、正文为HTML的邮件
2021/02/27 Python
阿迪达斯德国官方网站:adidas德国
2017/07/12 全球购物
应届护士求职信范文
2014/01/26 职场文书
幼儿园教师国培感言
2014/02/02 职场文书
超市优秀员工事迹材料
2014/05/01 职场文书
书法大赛策划方案
2014/06/04 职场文书
高速铁道技术专业求职信
2014/08/09 职场文书
祖国在我心中演讲稿600字
2014/09/23 职场文书
初中语文教师研修日志
2015/11/13 职场文书
Windows Server 2012配置DNS服务器的方法
2022/04/29 Servers