【问题标题】:Cannot get subprocess return code in python3无法在python3中获取子进程返回码
【发布时间】:2016-12-14 05:43:03
【问题描述】:

我正在尝试为我的 python 守护进程创建类似于 supervisor 的东西,并发现相同的代码在 python2 中有效,在 python3 中无效。

一般来说,我已经来到了这个最小的示例代码。

daemon.py

#!/usr/bin/env python

import signal
import sys
import os


def stop(*args, **kwargs):
    print('daemon exited', os.getpid())
    sys.exit(0)


signal.signal(signal.SIGTERM, stop)

print('daemon started', os.getpid())

while True:
    pass

supervisor.py

import os
import signal
import subprocess

from time import sleep


parent_pid = os.getpid()
commands = [
    [
        './daemon.py'
    ]
]
popen_list = []
for command in commands:
    popen = subprocess.Popen(command, preexec_fn=os.setsid)
    popen_list.append(popen)


def stop_workers(*args, **kwargs):
    for popen in popen_list:
        print('send_signal', popen.pid)
        popen.send_signal(signal.SIGTERM)

        while True:
            popen_return_code = popen.poll()
            if popen_return_code is not None:
                break
            sleep(5)


signal.signal(signal.SIGTERM, stop_workers)

for popen in popen_list:
    print('wait_main', popen.wait())

如果你运行 supervisor.py 然后在它的 pid 上调用kill -15,那么它将挂在无限循环中,因为 popen_return_code 永远不会不是 None。我发现,这基本上是因为为 wait_pid 操作添加 threading.Lock (source),但是我怎样才能重写代码以便正确处理子退出?

【问题讨论】:

    标签: python python-3.x subprocess


    【解决方案1】:

    这是一个有趣的案例。

    我花了几个小时试图找出发生这种情况的原因,此时我唯一想到的是 wait()poll() 的实现在 python3 与 @ 中发生了变化987654326@.

    查看python3/suprocess.py实现的源代码,我们可以看到调用Popen对象的wait()方法时发生了锁获取,见

    https://github.com/python/cpython/blob/master/Lib/subprocess.py#L1402.

    此锁可防止进一步的poll() 调用按预期工作,直到wait() 获得的锁将被释放,请参阅

    https://github.com/python/cpython/blob/master/Lib/subprocess.py#L1355

    并在那里发表评论

    其他东西正忙于调用 waitpid。不允许两个 立刻。我们还一无所知。

    python2.7/subprocess.py 中没有这样的锁,所以这似乎是它在 python2.7 中有效而在 python3 中无效的原因。

    但是我看不出您为什么要在信号处理程序中尝试 poll() 的原因,请尝试如下重写您的 supervisor.py,这应该在 python3python2.7 上都按预期工作

    supervisor.py

    import os
    import signal
    import subprocess
    
    from time import sleep
    
    
    parent_pid = os.getpid()
    commands = [
        [
            './daemon.py'
        ]
    ]
    popen_list = []
    for command in commands:
        popen = subprocess.Popen(command, preexec_fn=os.setsid)
        popen_list.append(popen)
    
    
    def stop_workers(*args, **kwargs):
        for popen in popen_list:
            print('send_signal', popen.pid)
            popen.send_signal(signal.SIGTERM)
    
    signal.signal(signal.SIGTERM, stop_workers)
    
    for popen in popen_list:
        print('wait_main', popen.wait())
    

    希望对你有帮助

    【讨论】:

    • 如果我不处理孩子存在并且只发送 SIGTERM,那么当我的孩子没有实现处理程序时(尝试从 daemon.py 中删除 sys.exit),它将与初始化进程分离.
    • 对于 popen_list 中的 popen: print('wait_main', popen.wait())
    • 对混乱的 cmets 感到抱歉 :(
    【解决方案2】:

    总的来说,我同意@risboo6909 的回答,但也有一些想法,如何解决这种情况。

    1. 您可以将subproccess.Popen 更改为psutil.Popen
    2. 在主循环而不是popen.wait() 中,您可以执行无限循环,因为进程将在信号处理程序中退出。

    【讨论】:

      猜你喜欢
      • 2020-10-04
      • 2010-12-23
      • 2019-08-04
      • 1970-01-01
      • 1970-01-01
      • 2018-08-07
      • 1970-01-01
      • 1970-01-01
      • 2018-09-09
      相关资源
      最近更新 更多