深入分析在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编程scoketServer实现多线程同步实例代码
Jan 29 Python
python多进程实现文件下载传输功能
Jul 28 Python
浅述python2与python3的简单区别
Sep 19 Python
对python遍历文件夹中的所有jpg文件的实例详解
Dec 08 Python
python采集微信公众号文章
Dec 20 Python
使用Python向C语言的链接库传递数组、结构体、指针类型的数据
Jan 29 Python
python如何给字典的键对应的值为字典项的字典赋值
Jul 05 Python
python实现身份证实名认证的方法实例
Nov 08 Python
win10下python2和python3共存问题解决方法
Dec 23 Python
Pytorch对Himmelblau函数的优化详解
Feb 29 Python
Python virtualenv虚拟环境实现过程解析
Apr 18 Python
浅谈Python3中print函数的换行
Aug 05 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中inlcude()性能对比详解
2012/09/16 PHP
PHP实现获取文件后缀名的几种常用方法
2015/08/08 PHP
php使用goto实现自动重启swoole、reactphp、workerman服务的代码
2020/04/13 PHP
WEB 浏览器兼容 推荐收藏
2010/05/14 Javascript
一个关于javascript匿名函数的问题分析
2012/03/30 Javascript
jquery 面包屑导航 具体实现
2013/06/05 Javascript
javascript闭包入门示例
2014/04/30 Javascript
JavaScript+CSS实现的可折叠二级菜单实例
2016/02/29 Javascript
jQuery的层级查找方式分析
2016/06/16 Javascript
微信 java 实现js-sdk 图片上传下载完整流程
2016/10/21 Javascript
canvas 实现中国象棋
2017/02/17 Javascript
layer实现关闭弹出层刷新父界面功能详解
2017/11/15 Javascript
Vue组件全局注册实现警告框的实例详解
2018/06/11 Javascript
React组件内事件传参实现tab切换的示例代码
2018/07/04 Javascript
JS 中可以提升幸福度的小技巧(可以识别更多另类写法)
2018/07/28 Javascript
npm 常用命令详解(小结)
2019/01/17 Javascript
Js视频播放器插件Video.js使用方法详解
2020/02/04 Javascript
python正则表达式中的括号匹配问题
2014/12/14 Python
Python爬取当当、京东、亚马逊图书信息代码实例
2017/12/09 Python
python 微信好友特征数据分析及可视化
2020/01/07 Python
HTML5 CSS3实现一个精美VCD包装盒个性幻灯片案例
2014/06/16 HTML / CSS
Nike加拿大官网:Nike.com (CA)
2019/04/09 全球购物
DOUGLAS荷兰:购买香水和化妆品
2020/10/24 全球购物
技术总监管理职责范本
2014/03/06 职场文书
投资意向书范本
2014/04/01 职场文书
中秋节主持词
2014/04/02 职场文书
2014年师德师风学习材料
2014/05/16 职场文书
公司年底活动方案
2014/08/17 职场文书
志愿者爱心公益活动策划方案
2014/09/15 职场文书
合同和协议有什么区别?
2014/10/08 职场文书
党的群众路线教育实践活动组织生活会发言材料
2014/10/17 职场文书
第28个世界无烟日活动总结
2015/02/10 职场文书
python获取对象信息的实例详解
2021/07/07 Python
5种方法告诉你如何使JavaScript 代码库更干净
2021/09/15 Javascript
Mysql Innodb存储引擎之索引与算法
2022/02/15 MySQL
Redis高并发缓存架构性能优化
2022/05/15 Redis