Python 确定自流程开始的时间';s最后一次输出-带有subprocess.Popen

Python 确定自流程开始的时间';s最后一次输出-带有subprocess.Popen,python,linux,subprocess,Python,Linux,Subprocess,我正在为测试套件中的进程编写一个看门狗。我需要确定测试是否挂起 我可以用subprocess.Popen(…)启动进程,然后使用Popen.wait(timeout=to)或Popen.poll()并保留自己的计时器。然而,这些测试在执行时间上差异很大,这使得不可能有一个对所有测试都合理的“超时”值 我发现确定测试是否挂起的一个好方法是在进程最后一次输出任何内容时设置一个“超时”。为此,我考虑使用 process = subprocess.Popen(args='<program>'

我正在为测试套件中的进程编写一个看门狗。我需要确定测试是否挂起

我可以用
subprocess.Popen(…)
启动进程,然后使用
Popen.wait(timeout=to)
Popen.poll()
并保留自己的计时器。然而,这些测试在执行时间上差异很大,这使得不可能有一个对所有测试都合理的“超时”值

我发现确定测试是否挂起的一个好方法是在进程最后一次输出任何内容时设置一个“超时”。为此,我考虑使用

process = subprocess.Popen(args='<program>', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ...)
这是一种被掩盖的

基本上,您需要使用
select()
轮询fd,查看他们是否有输入:

#!/usr/bin/python

import fcntl import os import select import subprocess


def setnonblocking(fd):
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    return fd

p = subprocess.Popen("/bin/sh -c 'c=10; while [ $c -gt 0 ]; do echo $c hello; sleep 1; >&2 echo world; sleep 1; let c=$c-1; done'", stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)

process_fds = map(setnonblocking, [p.stdout, p.stderr])

while process_fds:
    readable, writable, exceptional = select.select(process_fds, [], process_fds, 100)
    print "Select: ", readable, writable, exceptional
    print "Exitcode: ", p.poll()
    for fd in readable:
        data = os.read(fd.fileno(), 1024)
        if data == "":  # EOF
            process_fds.remove(fd)
            continue
        if fd == p.stdout:
            print "STDOUT: ",
        if fd == p.stderr:
            print "STDERR: ",
        print data,
    for fd in exceptional:
        process_fds.remove(fd)
输出:

Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  10 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  9 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
[...]
STDOUT:  1 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>, <open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  1
选择:[]
Exitcode:无
主持人:你好
选择:[]
Exitcode:无
斯特德:世界
选择:[]
Exitcode:无
STDOUT:9你好
选择:[]
Exitcode:无
[...]
主持人:你好
选择:[]
Exitcode:无
斯特德:世界
选择:[,][]
出口代码:1
使用
os.read()
代替
fd.read()
,因为您需要以非面向行的方式进行读取
fd.read()。使用此方法,您还可以拆分
stderr
stdout

编辑:修改为在
p.stdout
p.stderr

EOF
之前处理流程退出,这是一种覆盖

基本上,您需要使用
select()
轮询fd,查看他们是否有输入:

#!/usr/bin/python

import fcntl import os import select import subprocess


def setnonblocking(fd):
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    return fd

p = subprocess.Popen("/bin/sh -c 'c=10; while [ $c -gt 0 ]; do echo $c hello; sleep 1; >&2 echo world; sleep 1; let c=$c-1; done'", stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)

process_fds = map(setnonblocking, [p.stdout, p.stderr])

while process_fds:
    readable, writable, exceptional = select.select(process_fds, [], process_fds, 100)
    print "Select: ", readable, writable, exceptional
    print "Exitcode: ", p.poll()
    for fd in readable:
        data = os.read(fd.fileno(), 1024)
        if data == "":  # EOF
            process_fds.remove(fd)
            continue
        if fd == p.stdout:
            print "STDOUT: ",
        if fd == p.stderr:
            print "STDERR: ",
        print data,
    for fd in exceptional:
        process_fds.remove(fd)
输出:

Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  10 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>] [] []
Exitcode:  None
STDOUT:  9 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
[...]
STDOUT:  1 hello
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  None
STDERR:  world
Select:  [<open file '<fdopen>', mode 'rb' at 0x7fed75daa6f0>, <open file '<fdopen>', mode 'rb' at 0x7fed75daa660>] [] []
Exitcode:  1
选择:[]
Exitcode:无
主持人:你好
选择:[]
Exitcode:无
斯特德:世界
选择:[]
Exitcode:无
STDOUT:9你好
选择:[]
Exitcode:无
[...]
主持人:你好
选择:[]
Exitcode:无
斯特德:世界
选择:[,][]
出口代码:1
使用
os.read()
代替
fd.read()
,因为您需要以非面向行的方式进行读取
fd.read()。使用此方法,您还可以拆分
stderr
stdout

编辑:修改为在
p.stdout
p.stderr
EOF
之前处理进程退出

#!/usr/bin/env python3
import os
import selectors
import sys
from subprocess import Popen, PIPE, _PopenSelector as Selector

timeout = 1  # seconds
with Popen([sys.executable, '-c', '''import time
for i in range(10):  # dummy script
    time.sleep(i)
    print(i, flush=True)
'''], stdout=PIPE, stderr=PIPE) as process:
    pipes = {process.stdout: 1, process.stderr: 2}  # where to echo data
    with Selector() as sel:
        for pipe in pipes:
            os.set_blocking(pipe.fileno(), False)
            sel.register(pipe, selectors.EVENT_READ)
        while pipes:
            events = sel.select(timeout)
            if not events:  # timeout
                process.kill()
            for key, mask in events:
                assert mask == selectors.EVENT_READ
                data = os.read(key.fd, 512)
                if data == b'':  # EOF
                    sel.unregister(key.fileobj)
                    del pipes[key.fileobj]
                else:  # echo data
                    os.write(pipes[key.fileobj], data)
注意:循环未在
进程上终止。poll()
-没有数据丢失。代码使用与
子流程
作者喜欢的选择器相同的选择器,否则可以使用
sel=selectors.DefaultSelector()
。如果孙子进程可能继承管道,那么您应该更积极地在超时时中断循环()。要在Python 3.5之前实现os.set_blocking()
,可以使用:

下面介绍如何在Python 3中的Unix上实现“自子进程“上次输出”以来的超时:

#!/usr/bin/env python3
import os
import selectors
import sys
from subprocess import Popen, PIPE, _PopenSelector as Selector

timeout = 1  # seconds
with Popen([sys.executable, '-c', '''import time
for i in range(10):  # dummy script
    time.sleep(i)
    print(i, flush=True)
'''], stdout=PIPE, stderr=PIPE) as process:
    pipes = {process.stdout: 1, process.stderr: 2}  # where to echo data
    with Selector() as sel:
        for pipe in pipes:
            os.set_blocking(pipe.fileno(), False)
            sel.register(pipe, selectors.EVENT_READ)
        while pipes:
            events = sel.select(timeout)
            if not events:  # timeout
                process.kill()
            for key, mask in events:
                assert mask == selectors.EVENT_READ
                data = os.read(key.fd, 512)
                if data == b'':  # EOF
                    sel.unregister(key.fileobj)
                    del pipes[key.fileobj]
                else:  # echo data
                    os.write(pipes[key.fileobj], data)
注意:循环未在
进程上终止。poll()
-没有数据丢失。代码使用与
子流程
作者喜欢的选择器相同的选择器,否则可以使用
sel=selectors.DefaultSelector()
。如果孙子进程可能继承管道,那么您应该更积极地在超时时中断循环()。要在Python 3.5之前实现os.set_blocking()
,可以使用:


@J.F.塞巴斯蒂安谢谢你能听到!我修改了循环,直到两个FD都是EOF。在Python中使用
while some_list:
而不是
while len(some_list):
。我不确定将
select()
与阻塞fds一起使用是否安全。@J.F.Sebastian风格方面,我通常更喜欢len(list)的明确性。从
select()
系统调用手册页:如果可以在不阻塞的情况下执行相应的I/O操作(例如读取(2)),则认为文件描述符已准备就绪。这意味着fd可能会以其他方式阻止。使用
while len(items):
而不是
while items:
不是惯用法。您确定
select()
不能提前返回(例如在信号上)?此外,您可能应该在超时时退出循环。似乎
select()
是否以及如何被信号中断可能取决于平台和python版本(例如,可能引发异常,可能会自动重新启动)。我已经检查过了:
os.read()
可能会在
select.select()
之后阻塞(因为一些其他进程可能会消耗管道,或者(在套接字的情况下)由于网络堆栈实现中的一些奇怪之处:数据可能会报告就绪,然后由内核出于任何原因丢弃)..。@J.F.Sebastian谢谢你的关注!我修改了循环,直到两个FD都是EOF。在Python中使用
while some_list:
而不是
while len(some_list):
。我不确定将
select()
与阻塞fds一起使用是否安全。@J.F.Sebastian风格方面,我通常更喜欢len(list)的明确性。从
select()
系统调用手册页:如果可以在不阻塞的情况下执行相应的I/O操作(例如读取(2)),则认为文件描述符已准备就绪。这意味着fd可能会以其他方式阻止。使用
while len(items):
而不是
while items:
不是惯用法。您确定
select()
不能提前返回(例如在信号上)?此外,您可能应该在超时时退出循环。似乎
select()
是否以及如何被信号中断可能取决于平台和python版本(例如,可能引发异常,可能会自动重新启动)。我已经检查过了:
os.read()
可能会在
select.select()
之后阻塞(因为其他一些进程可能会消耗管道,或者(在套接字的情况下)由于网络堆栈实现中的一些奇怪之处:可能会报告读取数据