2010-09-17 3 views
18

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

Что-то вроде этого:

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

+1

связанные: [подпроцесс с таймаутом] (http://stackoverflow.com/q/1191374/4279) – jfs

ответ

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() 

В противном случае программа питона будет продолжать работать, пока таймер не закончит работу.

EDIT: Мне сообщили, что существует условие гонки, что подпроцесс 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 

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

+0

Этот код имеет состояние гонки. –

+0

Майк, не могли бы вы разработать или изменить исправление выше? Я использовал один и тот же код несколько раз, и если есть проблема, я бы определенно хотел его исправить. – dvntehn00bz

+1

'print 'Ошибка: процесс занимает слишком много времени, чтобы завершить - terminating'' может работать, даже если это ложь, и ваш процесс завершается без вашего убийства (потому что он делает это в моменты между вашими вызовами' poll' и 'kill'). –

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 секунд для того, чтобы закончить.

+3

Это заморозило бы этот процесс в течение 20 секунд. Это приемлемо? –

+3

разве этот вид не побеждает точку использования подпроцесса? – aaronasterling

+0

Кажется! Я думаю, что это небольшое неудобство в использовании подпроцесса. – sultan

2

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

+0

+1 Это было бы моим решением. – aaronasterling

+1

У меня тоже возникла такая проблема с файловыми дескрипторами процессов зомби. – sultan

+0

Султан. Должно быть возможно исправить это. Мне удалось окончательно отполировать мое приложение на что-то работоспособное, но оно не было достаточно общим для публикации. –

2

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

общий подход к отправке сигнала на подпроцесс:

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

Вы можете использовать этот механизм, чтобы прекратить после того, как тайм-аут период.

+0

Универсальный sys.stdout.flush() для всех приложений с использованием подпроцесса? – sultan

+2

Это очень похоже на ответ Абхишека. Он использует SIGKILL, и вы используете SIGUSR1. У этого есть те же проблемы. –

+0

Хорошо спасибо, ребята! – sultan

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

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

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 
3

питона подпроцесс автоматического тайм-аут не встроен, так что вы будете иметь, чтобы построить свой собственный.

Это работает для меня на 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 секунды (это не произойдет), процесс прекратится.

[email protected]:~$ python test.py 
[email protected]:~$ 

Существует три секунды между тем, когда процесс выполняется, и он завершается.

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

Я не тестировал в окнах; но, помимо обновления команды example, я думаю, что она должна работать, поскольку я не нашел в документации ничего, что говорит о том, что thread.join или process.terminate не поддерживается.

1

Да, https://pypi.python.org/pypi/python-subprocess2 продлит модуль POPEN с двумя дополнительными функциями,

Popen.waitUpTo(timeout=seconds) 

Это будет ждать до acertain количества секунд для завершения процесса, в противном случае возвращает None

также,

Popen.waitOrTerminate 

Это будет ждать до точки, а затем вызвать .terminate(), а затем .kill(), один orthe другой или какой-либо комбинации обоих, см документацию для получения полной информации:

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

Смежные вопросы