深入分析在Python模块顶层运行的代码引起的一个Bug
作者:Desmond Chen 发布时间:2021-06-29 01:26:29
然后我们在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变量与注释高级用法1.概述好的变量和注释并非为计算机而写,而是为每个阅读代码的人而写。变量与注释是表达作者思想的基础,他们对代码
- 注:所谓n位数“水仙花数”是指一个n数,其各位数字n次方和等于该数本身。如三位数“水仙花数”是指一个三位数,其各位数3次方和等于该数本身。一
- 本文实例讲述了django框架实现模板中获取request 的各种信息。分享给大家供大家参考,具体如下:在做网页程序时,request,re
- 一般我们安装Python的第三方包都会在终端执行下列命令进行安装:pip install 要安装的包名安装成功后发现在PyCharm中仍然存
- 这篇文章是为了对网络模型的权重输出,可以用来转换成其他框架的模型。import tensorflow as tffrom tensorflo
- 前提1.python环境及tensorflow安装成功2.Anaconda安装好 ,Anaconda安装步骤安装步骤1.下载facenet,
- 在Python的学习过程中,肯定会遇到很多安装模块的地方,可以使用easy_install安装,但是easy_install相对于pip而言
- PS: 我的检索是在文章模块下 forum/article第一步:先安装需要的包:pip install django-haystackpi
- 首先下载最新版本的python。www.python.org,目前版本为3.1。 接下来是安装,在windows下python的安装与其他应
- 用ASP.NET与SQL SERVER可是缘份最好了,稍大的程序一般第一先考虑的是SQL SERVER,只是一些很考虑经济的才使用ACCES
- 本文实例讲述了php计算两个整数的最大公约数常用算法。分享给大家供大家参考。具体如下:<?php//计时,返回秒function&nb
- 人们对于产品设计这类事情,往往容易眼高手低,在宇宙层面上夸夸其谈,却落不了地,只能飘着。真正到了自己动手的时候,才会发现问题很多,实践和理论
- python常见的错误有1.NameError变量名错误2.IndentationError代码缩进错误3.AttributeError对象
- 这篇文章主要介绍了pandas 空数据处理方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- 本文实例为大家分享了python web框架实现原生分页的具体代码,供大家参考,具体内容如下原生分页器 示例 &nbs
- 一、简介Paramiko模块是基于Python实现的SSH远程安全连接,用于SSH远程执行命令、文件传输等功能。安装模块默认Python没有
- 今天有朋友问到如下一则案例,ORA-01114,ORA-27067以及OSD-04026错误同时出现:*** ACTION NAME:()
- python 网络编程详解网络编程的专利权应该属于Unix,各个平台(如windows、Linux等)、各门语言(C、C++、Python、
- asp替换函数如下:Function ReplaceNoIgnoreCase(str,replStr) &n
- 许多共享主机的服务提供商不允许运行你自己的服务进程,也不允许修改 httpd.conf 文件。 尽管如此,仍然有可能通过Web服务器产生的子