【问题标题】:Why does Popen.poll() return a return code of None even though the sub-process has completed?为什么即使子进程已经完成,Popen.poll() 也会返回 None 的返回码?
【发布时间】:2012-11-08 22:01:42
【问题描述】:

我有一些在 Windows 上运行的 Python 代码,它生成一个子进程并等待它完成。子进程表现不佳,因此脚本会进行非阻塞生成调用并在旁边监视进程。如果达到某个超时阈值,它会终止进程,假设它已经脱离了轨道。

在某些不可重现的情况下,生成的子进程将消失,并且观察程序例程不会注意到这一事实。它会一直观察,直到超过超时阈值,尝试杀死子进程并得到错误,然后退出。

什么可能导致子进程已经消失而无法被观察者进程检测到?为什么调用Popen.poll()时没有捕获并返回返回码?

我用来生成和观察过程的代码如下:

import subprocess
import time

def nonblocking_subprocess_call(cmdline):
    print 'Calling: %s' % (' '.join(cmdline))
    p = subprocess.Popen(cmdline, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    return p


def monitor_subprocess(handle, timeout=1200):
    start_time = time.time()
    return_code = 0
    while True:
        time.sleep(60)
        return_code = handle.poll()
        if return_code == None:
            # The process is still running.
            if time.time() - start_time > timeout:
                print 'Timeout (%d seconds) exceeded -- killing process %i' % (timeout, handle.pid)
                return_code = handle.terminate()
                # give the kill command a few seconds to work
                time.sleep(5)
                if not return_code:
                    print 'Error: Failed to kill subprocess %i -- return code was: %s' % (handle.pid, str(return_code))
                # Raise an error to indicate that the process was hung up
                # and we had to kill it.
                raise RuntimeError
        else:
            print 'Process exited with return code: %i' % (return_code)
            break
    return return_code

我看到的是,在进程消失的情况下,第 15 行对 return_code = handle.poll() 的调用返回 None 而不是返回码。我知道这个过程已经完全消失了——我可以看到它不再存在于任务管理器中。而且我知道该进程在达到超时值之前很久就消失了。

【问题讨论】:

    标签: python windows subprocess


    【解决方案1】:

    你能举一个你的 cmdline 变量的例子吗?还有你在生成什么样的子进程?

    我在一个测试脚本上运行它,使用以下命令调用一个批处理文件:

    ping -n 151 127.0.0.1>nul
    
    • 睡眠 150 秒

    效果很好。

    可能是您的子进程未正确终止。另外,尝试将您的睡眠命令更改为类似 time.sleep(2) 的命令。

    在过去,我发现这比长时间睡眠更好(特别是如果您的子进程是另一个 python 进程)。

    另外,我不确定你的脚本是否有这个,但在 else: 语句中,你有一个额外的括号。

    else:
        #print 'Process exited with return code: %i' % (return_code))
        # There's an extra closing parenthesis
        print 'Process exited with return code: %i' % (return_code)
        break
    

    你怎么会在 join 语句中调用全局 temp_cmdline:

    print 'Calling: %s' % (' '.join(temp_cmdline))
    

    我不确定是从列表变量 temp_cmdline 解析 cmdline,还是从按空格分割的字符串创建 temp_cmdline。无论哪种方式,如果您的 cmdline 变量是一个字符串,那么打印它是否更有意义?

    print 'Calling: %s' % cmdline
    

    【讨论】:

    • 这是一个随机模拟命令,恐怕不能多说。每 1000 次处决中大约有 1 次发生这种情况,而且我从未能够可靠地重现它。额外的括号和 temp_cmdline 变量要归功于我在发布之前所做的一些清理工作,但是很好! :) cmdline 是一个列表。
    • 好的。这很好,因为我所做的最后一个子处理是围绕基于随机指标的预测工具的 GUI 包装器。它是在 python 中创建的,如果是,您使用的是什么库? PyMC?另外,你的模拟需要多长时间?在进行模拟时,您的代码是否需要运行其他东西?因为您可能只能调用 wait() 命令而不是轮询。如果它是一个基于 python 的模拟命令,也许你可以使用线程来代替。还有一件事,你试过把睡眠时间从 60 秒缩短到 2 秒吗?
    • 第三方命令是基于 C 的,我没有它的源代码。而且它有挂断电话的习惯,这就是实施轮询解决方案的原因。我会尝试减少睡眠时间。谢谢!
    【解决方案2】:

    对子进程对象的 poll 方法似乎不太好用。 当我产生一些线程来做一些工作时,我曾经遇到过同样的问题。 我建议你使用多处理模块。

    【讨论】:

    • 我现在正在使用该解决方案进行重构,但希望能对 Popen.poll() 的不稳定行为做出解释。
    【解决方案3】:

    如果 stdout 被其他东西捕获,Popen.poll 不会按预期工作,您可以检查取出这部分代码“, stdout=subprocess.PIPE”

    【讨论】:

      猜你喜欢
      • 2012-10-10
      • 2012-06-13
      • 1970-01-01
      • 1970-01-01
      • 2013-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-12
      相关资源
      最近更新 更多