Тайм-аут подпроцесса Python? - PullRequest
       15

Тайм-аут подпроцесса Python?

18 голосов
/ 17 сентября 2010

Есть ли аргумент или опции для установки тайм-аута для метода subprocess.Popen Python?

Примерно так:

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

Ответы [ 10 ]

15 голосов
/ 13 ноября 2012

Я бы посоветовал взглянуть на Класс таймера в модуле потоков. Я использовал его для реализации тайм-аута для 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

Хотя вы можете захотеть очистить обработку исключений, чтобы конкретно обрабатывать только определенное исключение, которое происходит, когда подпроцесс уже нормально завершился.

8 голосов
/ 17 сентября 2010

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 секунд до его завершения.

5 голосов
/ 14 июля 2014
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 не поддерживаются.

4 голосов
/ 17 сентября 2010

Вы могли бы сделать

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), ...)

с использованием API асинхронного процесса Twisted.

3 голосов
/ 11 декабря 2013

Тайм-аут подпроцесса Python не встроен, поэтому вам придется создать свой собственный.

Это работает для меня на Ubuntu 12.10 с Python 2.7.3

Поместите это в файл с именем 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:~$ 

Между запуском процесса и его остановкой проходит три секунды.

2 голосов
/ 17 сентября 2010

Нет времени нет.Я думаю, что вы ищете, чтобы убить подпроцесс через некоторое время.Поскольку вы можете сигнализировать о подпроцессе, вы также должны иметь возможность убить его.

универсальный подход к отправке сигнала подпроцессу:

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

Вы можете использовать этот механизм для завершения послепериод ожидания.

2 голосов
/ 17 сентября 2010

К сожалению, такого решения не существует. Мне удалось сделать это с помощью многопоточного таймера, который запустился бы вместе с процессом, который убил бы его после истечения времени ожидания, но я столкнулся с некоторыми устаревшими проблемами дескриптора файла из-за процессов зомби или чего-то подобного.

1 голос
/ 25 сентября 2015

Да, 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

1 голос
/ 21 августа 2014

Начиная с Python 3.3, также есть аргумент timeout для блокирующих вспомогательных функций в модуле подпроцесса.

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

0 голосов
/ 28 сентября 2013

Для 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
...