【发布时间】:2013-01-29 06:21:49
【问题描述】:
我有一个 python 程序,它使用Popen 启动子进程,并在生成时几乎实时地使用它们的输出。相关循环的代码为:
def run(self, output_consumer):
self.prepare_to_run()
popen_args = self.get_popen_args()
logging.debug("Calling popen with arguments %s" % popen_args)
self.popen = subprocess.Popen(**popen_args)
while True:
outdata = self.popen.stdout.readline()
if not outdata and self.popen.returncode is not None:
# Terminate when we've read all the output and the returncode is set
break
output_consumer.process_output(outdata)
self.popen.poll() # updates returncode so we can exit the loop
output_consumer.finish(self.popen.returncode)
self.post_run()
def get_popen_args(self):
return {
'args': self.command,
'shell': False, # Just being explicit for security's sake
'bufsize': 0, # More likely to see what's being printed as it happens
# Not guarantted since the process itself might buffer its output
# run `python -u` to unbuffer output of a python processes
'cwd': self.get_cwd(),
'env': self.get_environment(),
'stdout': subprocess.PIPE,
'stderr': subprocess.STDOUT,
'close_fds': True, # Doesn't seem to matter
}
这在我的生产机器上效果很好,但在我的开发机器上,当某些子进程完成时,对 .readline() 的调用会挂起。也就是说,它将成功处理所有输出,包括最后的输出行“处理完成”,但随后将再次轮询 readline 并且永远不会返回。对于我调用的大多数子进程,此方法在开发机器上正确退出,但对于一个本身调用许多子进程的复杂 bash 脚本始终无法退出。
值得注意的是,popen.returncode 在输出结束前多行被设置为非None(通常为0)值。所以我不能在设置循环时跳出循环,否则我会丢失在进程结束时吐出的所有内容,并且仍在缓冲等待读取。问题是当我在那个时候刷新缓冲区时,我不知道我什么时候结束,因为最后一次调用readline() 挂起。呼叫read() 也会挂起。调用read(1) 让我每一个字符都出来,但在最后一行之后也会挂起。 popen.stdout.closed 始终是 False。我怎么知道我什么时候结束了?
所有系统都在 Ubuntu 12.04LTS 上运行 python 2.7.3。 FWIW,stderr 正在使用 stderr=subprocess.STDOUT 与 stdout 合并。
为什么不一样?是否由于某种原因未能关闭stdout?子子流程能否以某种方式使其保持打开状态?可能是因为我是从我的开发盒上的终端启动进程,但在生产中它是通过supervisord 作为守护进程启动的?这会改变管道的处理方式吗?如果是,我该如何规范它们?
【问题讨论】:
-
问题不是你从一个不再存在的进程中读取一行吗?
-
我不这么认为。如果错误就这么简单,那么它会在任何时候都失败。
-
.poll()返回.returncode即可以直接测试。在末尾添加close_fds=True和self.popen.stdout.close()(清理)。bufsize是什么?您是否尝试过expect_unbuffer、stdbuf等来防止子进程侧的块缓冲?你可以试试select。 -
听上去,问题不在于 Python 代码,而在于这个“一个复杂的 bash 脚本”。您能否提供一个可用于重现故障的最小脚本示例?
-
您可以关闭子 bash 进程的管道 (stdout + stderr),当它的输出被这些命令
exec 1>&-exec 2>&-删除时,正如我在我的回答中描述的那样。我认为来自已接受答案的建议不如明确关闭管道那么强大。
标签: python python-2.7 popen