【问题标题】:how to get stdout of subprocess in python when receving SIGUSR2 /SIGINT接收 SIGUSR2 /SIGINT 时如何在 python 中获取子进程的标准输出
【发布时间】:2016-02-17 14:52:04
【问题描述】:

我有以下简单的 python 脚本:

import os, subprocess,signal,sys
import time

out = None
sub = None

def handler(signum,frame):
    print("script.py: cached sig: %i " % signum)
    sys.stdout.flush()

    if sub is not None and not sub.poll():
        print("render.py: sent signal to prman pid: ", sub.pid)
        sys.stdout.flush()
        sub.send_signal(signal.SIGTERM)
        sub.wait() # deadlocks....????
        #os.kill(sub.pid, signal.SIGTERM)  # this works
        #os.waitpid(sub.pid,0)             # this works

    for i in range(0,5):
        time.sleep(0.1)
        print("script.py: cleanup %i" % i)
        sys.stdout.flush()

    sys.exit(128+signum)

signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGUSR2, handler)
signal.signal(signal.SIGTERM, handler)

sub = subprocess.Popen(["./doStuff.sh"], stderr = subprocess.STDOUT)
sub.wait()


print("finished script.py")

doStuff.sh

#!/bin/bash

function trap_with_arg() {
    func="$1" ; shift
    for sig ; do
        trap "$func $sig" "$sig"
    done
}

pid=False

function signalHandler() {

    trap - SIGINT SIGTERM

    echo "doStuff.sh chached sig: $1"
    echo "doStuff.sh cleanup: wait 10s"
    sleep 10s

    # kill ourself to signal calling process we exited on SIGINT
    kill -s SIGINT $$

}

trap_with_arg signalHandler SIGINT SIGTERM
trap "echo 'doStuff.sh ignore SIGUSR2'" SIGUSR2 
# ignore SIGUSR2

echo "doStuff.sh : pid:  $$"
echo "doStuff.sh: some stub error" 1>&2
for i in {1..100}; do
    sleep 1s
    echo "doStuff.sh, rendering $i"
done

当我发送在终端中启动的进程时 python3 scripts.py & 带有kill -USR2 -$! 的信号 该脚本捕获 SIGINT,并在 sub.wait() 中永远等待,ps -uf 显示以下内容:。

user   27515  0.0  0.0  29892  8952 pts/22   S    21:56   0:00  \_ python script.py
user   27520  0.0  0.0      0     0 pts/22   Z    21:56   0:00      \_ [doStuff.sh] <defunct>

请注意doStuff.sh 正确处理 SIGINT 并退出。

我还想在调用handler 时获得stdout 的输出?如何正确执行此操作?

非常感谢!

【问题讨论】:

  • 我无法重现该行为(您的操作系统、shell、python 版本是什么?)。你能提供一个虚拟的dostuff.py 作为例子吗?为什么用-$!而不是$!——前者可能会向整个进程组发送信号?
  • 我发送到整个进程组,因为我在集群上运行它,它向整个进程组发送 SIGUSR2 信号。
  • 我更新了答案,并提供了 doStuff.sh。你能在你的机器上试试这个吗,在我的这个死锁上给出如上所示的进程列表输出
  • 不相关的代码太多了。这是minimal code example that shows that send_signal() works
  • 我更新了the minimal example 以证明child.wait() 挂在信号处理程序中。您问题中的代码也挂起(出于同样的原因)。

标签: python python-3.x subprocess signals


【解决方案1】:

您的代码无法获取子进程的标准输出,因为它在调用 subprocess.Popen() 时不会重定向其标准流。在信号处理程序中对此进行任何处理都为时已晚。

如果你想捕获标准输出然后传递stdout=subprocess.PIPE 并调用.communicate() 而不是.wait()

child = subprocess.Popen(command, stdout=subprocess.PIPE)
output = child.communicate()[0]

信号处理程序在 Python 3 上的 .wait() 调用上挂起是一个完全独立的问题(Python 2 或 os.waitpid() 不会在此处挂起,而是收到了错误的子退出状态)。这里是a minimal code example to reproduce the issue

#!/usr/bin/env python
import signal
import subprocess
import sys


def sighandler(*args):
    child.send_signal(signal.SIGINT)
    child.wait()  # It hangs on Python 3 due to child._waitpid_lock

signal.signal(signal.SIGUSR1, sighandler)
child = subprocess.Popen([sys.executable, 'child.py'])
sys.exit("From parent %d" % child.wait())  # return child's exit status

child.py:

#!/usr/bin/env python
"""Called from parent.py"""
import sys
import time

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:  # handle SIGINT
    sys.exit('child exits on KeyboardInterrupt')

例子:

$ python3 parent.py &
$ kill -USR1 $!
child exits on KeyboardInterrupt
$ fg
... running    python3 parent.py

示例显示子进程已退出,但父进程仍在运行。如果按 Ctrl+C 打断它;回溯显示它挂在.wait() 调用内的with _self._waitpid_lock: 语句上。如果将subprocess.py中的self._waitpid_lock = threading.Lock()替换为self._waitpid_lock = threading.RLock(),则效果与使用os.waitpid()相同——不会挂起,但退出状态不正确。

为了避免这个问题,不要在信号处理程序中等待孩子的状态:调用send_signal(),设置一个简单的布尔标志并从处理程序返回。在主代码中,检查child.wait() 之后的标志(问题代码中print("finished script.py") 之前),查看是否收到信号(如果从child.returncode 不清楚)。如果设置了标志;调用相应的清理代码并退出。

【讨论】:

    【解决方案2】:

    您应该查看 subprocess.check_output

    proc_output = subprocess.check_output(commands_list, stderr=subprocess.STDOUT)
    

    你可以将它包围在 try except 中,然后:

    except subprocess.CalledProcessError, error:
        create_log = u"Creation Failed with return code {return_code}\n{proc_output}".format(
            return_code=error.returncode, proc_output=error.output
        )
    

    【讨论】:

    • try: out = subprocess.check_output(["command"]) except subprocess.CalledProcessError as error: print(error.output) ) --> 信号到达时异常何时被调用?我没有看到打印的异常?
    • @Gabriel 您必须将信号从您的处理程序发送到子进程,然后它将捕获它。
    • @尼尔,感谢您的更新。我确实试过了,但是sub.wait() 卡住了(请参阅更新的答案)。你知道怎么做吗?
    • @Gabriel 你得到输出“向命令发送信号”吗?我猜您需要在处理程序方法def handler(signum,frame):\nglobal sub 中将 sub 定义为全局
    • 看我解决问题的方法,很奇怪,感谢输入,我更正了问题
    【解决方案3】:

    我只能等待进程使用

      os.kill(sub.pid, signal.SIGINT)
      os.waitpid(sub.pid,0)
    

    而不是

      sub.send_signal(signal.SIGINT)
      sub.wait() # blocks forever
    

    这与UNIX上的进程组有关,我不太明白:我认为进程./doStuff.sh没有收到信号,因为同一进程组中的子进程没有收到信号。 (我不确定这是否正确)。希望有人可以详细说明这个问题。

    处理程序被调用之前的输出被推送到调用 bash(控制台)的标准输出。

    【讨论】:

    • 代码示例之间没有本质区别。 .send_signal(sig) 在内部使用 os.kill(self.pid, sig).wait() 在内部使用 os.waitpid(self.pid, 0)。它与 Unix 上的进程组无关。
    • 好的,所以我不明白它应该挂在那里吗?也许我应该尝试一个最小的例子
    • 我的猜测:它挂起是因为sub.wait() 在信号处理程序运行时持有sub._waitpid_lock 锁定,因此您不应该在处理程序内部调用sub.wait()——也许,这是一个错误Python(应使用 RLock 而不是 Lock)。你应该create a minimal code example that demonstrates the issue
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 1970-01-01
    • 2016-11-17
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    • 1970-01-01
    相关资源
    最近更新 更多