接收SIGUSR2/SIGINT时如何在python中获取子进程的标准输出 [英] how to get stdout of subprocess in python when receving SIGUSR2 /SIGINT

查看:29
本文介绍了接收SIGUSR2/SIGINT时如何在python中获取子进程的标准输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下简单的 python 脚本:

I have the following simple python script:

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 显示以下内容:.

when I send the process launched in a terminal by python3 scripts.py & a signal with kill -USR2 -$! the script catches the SIGINT, and waits forever in sub.wait(), a ps -uf shows the following:.

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 并退出.

Be aware that doStuff.sh properly handles SIGINT and quits.

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

I would also like to get the output of stdout when the handler is called? How to do this properly?

非常感谢!

推荐答案

您的代码无法获取子进程的标准输出,因为它在调用 subprocess.Popen().在信号处理程序中做任何事情都为时已晚.

Your code can't get the child process' stdout because it doesn't redirect its standard streams while calling subprocess.Popen(). It is too late to do anything about it in the signal handler.

如果您想捕获标准输出,则通过 stdout=subprocess.PIPE 并调用 .communicate() 而不是 .wait():

If you want to capture stdout then pass stdout=subprocess.PIPE and call .communicate() instead of .wait():

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

<小时>

有一个完全独立的问题,即信号处理程序挂在 Python 3 上的 .wait() 调用上(Python 2 或 os.waitpid() 不挂起)在这里,而是收到了错误的孩子的退出状态).这是重现问题的最小代码示例:


There is a completely separate issue that the signal handler hangs on the .wait() call on Python 3 (Python 2 or os.waitpid() do not hang here but a wrong child's exit status is received instead). Here's 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 来中断它;回溯显示它挂在 with _self._waitpid_lock: 语句中 .wait() 调用.如果在 subprocess.py 中将 self._waitpid_lock = threading.Lock() 替换为 self._waitpid_lock = threading.RLock() 那么效果与使用 os.waitpid() 相同——它不会挂起,但退出状态不正确.

The example shows that the child has exited but the parent is still running. If you press Ctrl+C to interrupt it; the traceback shows that it hangs on with _self._waitpid_lock: statement inside the .wait() call. If self._waitpid_lock = threading.Lock() is replaced with self._waitpid_lock = threading.RLock() in subprocess.py then the effect is the same as using os.waitpid() -- it doesn't hang but the exit status is incorrect.

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

To avoid the issue, do not wait for child's status in the signal handler: call send_signal(), set a simple boolean flag and return from the hanlder instead. In the main code, check the flag after child.wait() (before print("finished script.py") in your code in the question), to see whether the signal has been received (if it is not clear from child.returncode). If the flag is set; call the appropriate cleanup code and exit.

这篇关于接收SIGUSR2/SIGINT时如何在python中获取子进程的标准输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆