Python子进程超时?

时间:2010-09-17 07:00:17

标签: python timeout pipe subprocess popen

是否有任何参数或选项可以为Python的subprocess.Popen方法设置超时?

这样的事情:

subprocess.Popen(['..'], ..., timeout=20)

10 个答案:

答案 0 :(得分:15)

我建议您查看线程模块中的Timer class。我用它来实现Popen的超时。

首先,创建一个回调:

    def timeout( p ):
        if p.poll() is None:
            print 'Error: process taking too long to complete--terminating'
            p.kill()

然后打开过程:

    proc = Popen( ... )

然后创建一个定时器,调用回调将进程传递给它。

    t = threading.Timer( 10.0, timeout, [proc] )
    t.start()
    t.join()

在程序后面的某个地方,您可能想要添加以下行:

    t.cancel()

否则,python程序将一直运行,直到计时器运行完毕。

编辑:我被告知存在竞争条件,子进程p可能在p.poll()和p.kill()调用之间终止。我相信以下代码可以解决这个问题:

    import errno

    def timeout( p ):
        if p.poll() is None:
            try:
                p.kill()
                print 'Error: process taking too long to complete--terminating'
            except OSError as e:
                if e.errno != errno.ESRCH:
                    raise

虽然您可能希望清除异常处理,以专门处理子进程已正常终止时发生的特定异常。

答案 1 :(得分:8)

subprocess.Popen没有阻止,所以你可以这样做:

import time

p = subprocess.Popen(['...'])
time.sleep(20)
if p.poll() is None:
  p.kill()
  print 'timed out'
else:
  print p.communicate()

它有一个缺点,你必须等待至少20秒才能完成。

答案 2 :(得分:5)

import subprocess, threading

class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None

    def run(self, timeout):
        def target():
            print 'Thread started'
            self.process = subprocess.Popen(self.cmd, shell=True)
            self.process.communicate()
            print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process'
            self.process.terminate()
            thread.join()
        print self.process.returncode

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)

这个的输出应该是:

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

可以看出,在第一次执行中,进程正确完成(返回代码0),而在第二次执行中进程终止(返回代码-15)。

我还没有在Windows中测试过;但是,除了更新示例命令之外,我认为它应该可行,因为我在文档中找不到任何说明不支持thread.join或process.terminate的内容。

答案 3 :(得分:4)

你可以做到

from twisted.internet import reactor, protocol, error, defer

class DyingProcessProtocol(protocol.ProcessProtocol):
    def __init__(self, timeout):
        self.timeout = timeout

    def connectionMade(self):
        @defer.inlineCallbacks
        def killIfAlive():
            try:
                yield self.transport.signalProcess('KILL')
            except error.ProcessExitedAlready:
                pass

        d = reactor.callLater(self.timeout, killIfAlive)

reactor.spawnProcess(DyingProcessProtocol(20), ...)

使用Twisted的异步流程API。

答案 4 :(得分:3)

没有内置python子进程自动超时,因此您必须自己构建。

这适用于运行python 2.7.3的Ubuntu 12.10

将其放在名为test.py

的文件中
#!/usr/bin/python
import subprocess
import threading

class RunMyCmd(threading.Thread):
    def __init__(self, cmd, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd 
        self.timeout = timeout

    def run(self):
        self.p = subprocess.Popen(self.cmd)
        self.p.wait()

    def run_the_process(self):
        self.start()
        self.join(self.timeout)

        if self.is_alive():
            self.p.terminate()   #if your process needs a kill -9 to make 
                                 #it go away, use self.p.kill() here instead.

            self.join()

RunMyCmd(["sleep", "20"], 3).run_the_process()

保存并运行它:

python test.py

sleep 20命令需要20秒才能完成。如果它没有在3秒内终止(它不会终止),则该过程终止。

el@apollo:~$  python test.py 
el@apollo:~$ 

进程运行之间有三秒钟,并且终止。

答案 5 :(得分:2)

不幸的是,没有这样的解决方案。我设法使用一个线程计时器来执行此操作,该计时器将与在超时后将其终止的进程一起启动但由于僵尸进程或其他一些原因,我确实遇到了一些陈旧的文件描述符问题。

答案 6 :(得分:2)

没有时间没有。我想,你要找的是在一段时间后杀死子过程。既然你能够发信号通知子进程,你也应该能够杀死它。

向子进程发送信号的一般方法:

proc = subprocess.Popen([command])
time.sleep(1)
print 'signaling child'
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)

您可以使用此机制在超时后终止。

答案 7 :(得分:1)

从Python 3.3开始,子进程模块中的阻塞辅助函数也有一个timeout参数。

https://docs.python.org/3/library/subprocess.html

答案 8 :(得分:1)

是的,https://pypi.python.org/pypi/python-subprocess2将使用另外两个函数扩展Popen模块,

Popen.waitUpTo(timeout=seconds)

这将等待进程完成的某个秒数,否则返回None

Popen.waitOrTerminate

这将等待一个点,然后调用.terminate(),然后调用.kill(),其中一个或两者的组合,请参阅文档以获取完整的详细信息:

http://htmlpreview.github.io/?https://github.com/kata198/python-subprocess2/blob/master/doc/subprocess2.html

答案 9 :(得分:0)

对于Linux,您可以使用信号。这取决于平台,因此Windows需要另一种解决方案。它可能适用于Mac。

def launch_cmd(cmd, timeout=0):
    '''Launch an external command

    It launchs the program redirecting the program's STDIO
    to a communication pipe, and appends those responses to
    a list.  Waits for the program to exit, then returns the
    ouput lines.

    Args:
        cmd: command Line of the external program to launch
        time: time to wait for the command to complete, 0 for indefinitely
    Returns:
        A list of the response lines from the program    
    '''

    import subprocess
    import signal

    class Alarm(Exception):
        pass

    def alarm_handler(signum, frame):
        raise Alarm

    lines = []

    if not launch_cmd.init:
        launch_cmd.init = True
        signal.signal(signal.SIGALRM, alarm_handler)

    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    signal.alarm(timeout)  # timeout sec

    try:
        for line in p.stdout:
            lines.append(line.rstrip())
        p.wait()
        signal.alarm(0)  # disable alarm
    except:
        print "launch_cmd taking too long!"
        p.kill()

    return lines        
launch_cmd.init = False