深入分析在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 文件操作技巧(File operation) 实例代码分析
Aug 11 Python
老生常谈Python序列化和反序列化
Jun 28 Python
python 对key为时间的dict排序方法
Oct 17 Python
Python文件常见操作实例分析【读写、遍历】
Dec 10 Python
python模拟登陆,用session维持回话的实例
Dec 27 Python
Python图像滤波处理操作示例【基于ImageFilter类】
Jan 03 Python
使用coverage统计python web项目代码覆盖率的方法详解
Aug 05 Python
Python数组并集交集补集代码实例
Feb 18 Python
在服务器上安装python3.8.2环境的教程详解
Apr 26 Python
详解基于python的全局与局部序列比对的实现(DNA)
Oct 07 Python
Python类绑定方法及非绑定方法实例解析
Oct 09 Python
用Python编写简单的gRPC服务的详细过程
Jul 04 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
jQuery获取json后使用zy_tmpl生成下拉菜单
2015/03/27 PHP
织梦sitemap地图实时推送给百度的教程
2015/08/03 PHP
PHP对象克隆clone用法示例
2016/09/28 PHP
更正确的asp冒泡排序
2007/05/24 Javascript
javascript HTMLEncode HTMLDecode的完整实例(兼容ie和火狐)
2009/06/02 Javascript
jQuery 使用手册(七)
2009/09/23 Javascript
JavaScript isPrototypeOf和hasOwnProperty使用区别
2010/03/04 Javascript
js操作时间(年-月-日 时-分-秒 星期几)
2010/06/20 Javascript
javaScript NameSpace 简单说明介绍
2013/07/18 Javascript
解析JavaScript中点号“.”的多义性
2013/12/02 Javascript
自写的jQuery异步加载数据添加事件
2014/05/15 Javascript
使用变量动态设置js的属性名
2014/10/19 Javascript
JQuery右键菜单插件ContextMenu使用指南
2014/12/19 Javascript
javascript实现时间格式输出FormatDate函数
2015/01/13 Javascript
javascript基于DOM实现省市级联下拉框的方法
2015/05/14 Javascript
jQuery切换所有复选框选中状态的方法
2015/07/02 Javascript
js实现仿百度风云榜可重复多次调用的TAB切换选项卡效果
2015/08/31 Javascript
跟我学习javascript的异步脚本加载
2015/11/20 Javascript
jQuery使用$.ajax进行即时验证实例详解
2015/12/11 Javascript
jQuery基于ID调用指定iframe页面内的方法
2016/07/06 Javascript
微信小程序实现侧边分类栏
2019/10/21 Javascript
js页面加载后执行的几种方式小结
2020/01/30 Javascript
Element Carousel 走马灯的具体实现
2020/07/26 Javascript
vue3为什么要用proxy替代defineProperty
2020/10/19 Javascript
python写的一个squid访问日志分析的小程序
2014/09/17 Python
对Python中列表和数组的赋值,浅拷贝和深拷贝的实例讲解
2018/06/28 Python
如何用OpenCV -python3实现视频物体追踪
2019/12/04 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
2019/12/12 Python
pytorch实现保证每次运行使用的随机数都相同
2020/02/20 Python
构建高效的python requests长连接池详解
2020/05/02 Python
美国保健品专家:Life Extension
2018/05/04 全球购物
转党组织关系介绍信
2014/01/08 职场文书
公司离职证明范本
2014/01/13 职场文书
三年级班级文化建设方案
2014/05/04 职场文书
企业员工爱岗敬业演讲稿
2014/08/26 职场文书
PostgreSQL聚合函数介绍以及分组和排序
2022/04/12 PostgreSQL