深入分析在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类的动态修改的实例方法
Mar 24 Python
使用Python实现简单的服务器功能
Aug 25 Python
Python之批量创建文件的实例讲解
May 10 Python
django传值给模板, 再用JS接收并进行操作的实例
May 28 Python
Python subprocess模块常见用法分析
Jun 12 Python
Python告诉你木马程序的键盘记录原理
Feb 02 Python
解决pycharm下os.system执行命令返回有中文乱码的问题
Jul 07 Python
如何使用Python脚本实现文件拷贝
Nov 20 Python
Python创建空列表的字典2种方法详解
Feb 13 Python
python GUI库图形界面开发之PyQt5 UI主线程与耗时线程分离详细方法实例
Feb 26 Python
浅谈keras.callbacks设置模型保存策略
Jun 18 Python
python 如何执行控制台命令与操作剪切板
May 20 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实现自动获取生成文章主题关键词功能的深入分析
2013/06/03 PHP
php使用百度天气接口示例
2014/04/22 PHP
smarty模板引擎之内建函数用法
2015/03/30 PHP
Laravel中的Blade模板引擎示例详解
2017/10/10 PHP
才发现的超链接js导致网页中GIF动画停止的解决方法
2007/11/02 Javascript
javascript中字符串拼接需注意的问题
2010/07/13 Javascript
IE无法设置短域名下Cookie
2010/09/23 Javascript
jQuery(1.6.3) 中css方法对浮动的实现缺陷分析
2011/09/09 Javascript
获取客户端电脑日期时间js代码(jquery)
2012/09/12 Javascript
JS 实现导航栏悬停效果(续)
2013/09/24 Javascript
JavaScript检测上传文件大小的方法
2015/07/22 Javascript
拥有一个属于自己的javascript表单验证插件
2016/03/24 Javascript
使用jQuery制作浮动工具栏的实例分享
2016/05/13 Javascript
jquery实现网站列表切换效果的2种方法
2016/08/12 Javascript
javascript垃圾收集机制的原理分析
2016/12/08 Javascript
vue 项目接口管理的实现
2019/01/17 Javascript
关于js陀螺仪的理解分析
2019/04/11 Javascript
在微信小程序中使用图表的方法示例
2019/04/25 Javascript
vue 父组件中调用子组件函数的方法
2019/06/06 Javascript
微信小程序实现选项卡滑动切换
2020/10/22 Javascript
[02:51]2014DOTA2 TI小组赛总结中国军团全部进军钥匙球馆
2014/07/15 DOTA
[02:05]DOTA2完美大师赛趣味视频之看我表演
2017/11/18 DOTA
[03:11]完美世界DOTA2联赛PWL DAY8集锦
2020/11/09 DOTA
Python装饰器的函数式编程详解
2015/02/27 Python
windows下 兼容Python2和Python3的解决方法
2018/12/05 Python
Pandas时间序列重采样(resample)方法中closed、label的作用详解
2019/12/10 Python
pymongo insert_many 批量插入的实例
2020/12/05 Python
css3的过滤效果简单实例
2016/08/03 HTML / CSS
html5使用canvas画一条线
2014/12/15 HTML / CSS
安全责任书
2015/01/29 职场文书
同步小康驻村工作简报
2015/07/20 职场文书
求职自荐信该如何书写?
2019/06/24 职场文书
导游词之秦皇岛燕塞湖
2020/01/03 职场文书
Django项目配置Memcached和Redis, 缓存选择哪个更有优势
2021/04/06 Python
提取视频中的音频 Python只需要三行代码!
2021/05/10 Python
Redis可视化客户端小结
2021/06/10 Redis