【问题标题】:How do I properly handle Python 3's Popen.communicate() timeout?如何正确处理 Python 3 的 Popen.communicate() 超时?
【发布时间】:2016-04-02 01:20:47
【问题描述】:

我发现Popen.communicate() 上的文档有些不完整。以文档中的示例为例,稍作修改:

p = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
try :                                                                       
    outs, errs = p.communicate(ins, timeout=5)                             
except TimeoutExpired:                                                      
    p.kill()                                                
    outs, errs = p.communicate(ins) # What's with the input data?                                         
# What value does p.returncode have at this point?

那我有两个问题:

  1. 如果我将输入ins 发送到子进程,是否在捕获超时异常后重新发送输入?此句柄是否已正确读取输入?
  2. 在调用p.kill() 之后,即在向进程发送SIGKILL 之后,p.returncode 的值(如果有)是多少?

【问题讨论】:

    标签: python-3.x subprocess


    【解决方案1】:
    1. 杀死进程后,发送输入没有意义。最后的p.communicate 读取管道中的数据。

    2. 不确定。该进程可能会在异常处理程序中的超时和p.kill 之间自行退出,无论退出代码如何。否则,p.returncode 文档会说

      “负值 -N 表示子节点被信号 N 终止(仅限 POSIX)。”

      所以 -SIGKILL 在 POSIX 上。在 Windows 上?如果这对您很重要,请尝试一下。

    【讨论】:

    • 谢谢,这是有道理的。我实际上很想在kill 之后完全跳过communicate,因为无论如何数据很可能是不完整的——至少在我的情况下是这样。 (虽然在 POSIX 上,我似乎应该能够检查 returncode 并查看该过程是否在 kill 之前完成。)我刚刚添加了一个指向 other Popen.returncode 文档的链接与您的引文;我第一次没有看到那个。
    • @Jens: .returncode 是 None 直到您调用 .wait().communicate() 并且它们成功返回(或 .poll() 返回非 None 值)。第二个.communicate() 调用应该删除ins 参数,即使用p.communicate(),而不是p.communicate(ins)。即使您不需要缓冲输出,也要使用p.communicate() 来关闭管道。
    【解决方案2】:

    我想我可以通过阅读subprocess code 来避免发布这个问题:) 请注意,该模块为POSIX 和 Windows 实现了一些不同的内部结构,此时我只关心 POSIX 实现。

    所以这是我对这一切的看法,它在很大程度上验证了已经回答的问题。

    1. 在超时杀死子进程后再次将ins 传递给communicate() 将引发ValueError 异常,指出Cannot send input after starting communication(请参阅here)。

    2. 请注意,Terry's answer 涵盖了这一点。 returncode 最初设置为 None

      • 如果子进程在引发超时异常和发送终止信号之间终止,则kill() 将不会发送信号(请参阅here)并且对communicate() 的第二次调用将简单地收集输出数据和returncode 包含一个正确的值。

      • 否则第二次调用communicate()will wait()直到子进程死亡,将返回码值设置为-SIGKILL(参见here)作为等待的一部分,然后收集输出数据。

      这意味着我可以在问题中的 try-except 块之后使用p.returncode,它会告诉我子进程是否正确终止。

    【讨论】:

    • I have to repeat: .returncode 不会神奇地自我更新。您必须致电.wait().communicate().poll() 来更新它。因此,如果 .communicate() 引发 TimeoutError.returncodeNone 总是 - 没有竞争条件。如果你没有重定向stdin/stdout/stderr,你应该在.kill()调用之后调用.wait()设置.returncode,否则调用.communicate()关闭管道并设置.returncode
    • 实际上,在我的场景中,.communicate()kill() 之后重复锁定,我可以将其追溯到select(timeout=None) 调用here。只有输出通过管道传输并为该 SelectSelector 注册。不太清楚这里到底发生了什么......但是。
    • 这意味着子进程产生了自己的子进程,这些子进程继承了管道并在.kill() 中存活下来(它们的父进程被他们继续杀死)。如何杀死任意进程家族取决于应用程序。如果子进程不更改他们的进程组,则使用:preexec_fn=os.setpgrpos.killpg(p.pid, signal.SIGINT) 杀死整个进程组。如果您不关心幸存的后代,请手动关闭管道 (pipe.close()) 并在 p.kill() 之后调用 p.wait()
    • 你指的是this solution,对吗?
    • 是的,类似。虽然注意细节有所不同。
    猜你喜欢
    • 2018-05-27
    • 2022-01-16
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-25
    相关资源
    最近更新 更多