然后我们在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中的urllib2模块,包括了深入解析Python中的urllib2模块的使用技巧和注意事项,需要的朋友参考一下 Python 标准库中有很多实用的工具类,但是在具体使用时,标准库文档上对使用细节描述的并不清楚,比如 urllib2 这个 HTTP 客户端库。这里总结了一些 urllib2 的使用细节。 Proxy 的设置 Timeout 设置 在 HTTP Re
本文向大家介绍深入浅析Python 命令行模块 Click,包括了深入浅析Python 命令行模块 Click的使用技巧和注意事项,需要的朋友参考一下 Click 是用 Python 写的一个第三方模块,用于快速创建命令行。我们知道,Python 内置了一个 Argparse 的标准库用于创建命令行,但使用起来有些繁琐,Click 相比于 Argparse,就好比 requests 相比于 url
本文向大家介绍深入浅析Nodejs的Http模块,包括了深入浅析Nodejs的Http模块的使用技巧和注意事项,需要的朋友参考一下 一、http服务器 我们知道传统的HTTP服务器是由Aphche、Nginx、IIS之类的软件来搭建的,但是Nodejs并不需要,Nodejs提供了http模块,自身就可以用来构建服务器,例如: http模块中封装了一个HTTP服务器和一个简易的HTTP客户端
问题内容: 我有这个简单的python函数,可以提取一个zip文件(与平台无关) 这在Python 2.7上运行良好,但在Python 2.6上失败: 我发现此建议要求升级2.6-> 2.7 https://bugs.launchpad.net/horizon/+bug/955994 但是是否可以移植上面的代码以使其与Python 2.6一起使用,并且仍然保持跨平台运行? 问题答案: 关于什么
本文向大家介绍深入分析PHP引用(&),包括了深入分析PHP引用(&)的使用技巧和注意事项,需要的朋友参考一下 引用是什么 在 PHP 中引用意味着用不同的名字访问同一个变量内容。这并不像 C 的指针,替代的是,引用是符号表别名。注意在 PHP 中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是 Unix 的文件名和文件本身——变量名是目录条目,而变量内容则是文件本身。
本文向大家介绍深入解析Python编程中JSON模块的使用,包括了深入解析Python编程中JSON模块的使用的使用技巧和注意事项,需要的朋友参考一下 JSON编码支持的基本数据类型为 None , bool , int , float 和 str , 以及包含这些类型数据的lists,tuples和dictionaries。 对于dictionaries,keys需要是字符串类型(字典中任何非字