2010-09-17 20 views
18

Czy jest jakiś argument lub opcje do ustawienia limitu czasu dla podprocesu Pythona?Timeout podprocesu w języku Python?

coś takiego:

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

+1

pokrewnych: [podproces z limitem czasu] (http://stackoverflow.com/q/1191374/4279) – jfs

Odpowiedz

15

Radzę rzucić okiem na Timer class w module wątków. Użyłem go do wprowadzenia limitu czasu dla Popen.

Najpierw utwórz callback:

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

następnie otworzyć proces:

proc = Popen(...) 

następnie utworzyć zegar, który nazywamy zwrotnego przechodzącego proces do niego.

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

Gdzieś później w programie, może chcesz dodać linię:

t.cancel() 

W przeciwnym razie program python będzie biec aż timer zakończy działanie.

EDYCJA: Zostałem poinformowany, że istnieje warunek wyścigu, że podproces p może zakończyć się między wywołaniami p.poll() i p.kill(). Wierzę, że następujący kod można ustalić, że:

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 

Choć może chcesz wyczyścić wyjątek obsługi specjalnie obsługiwać tylko szczególny wyjątek, który występuje, gdy podproces został już zakończony normalnie.

+0

Ten kod ma stan wyścigu. –

+0

Mike, czy mógłbyś rozwinąć lub poprawić powyższą poprawkę? Kilka razy użyłem podobnego kodu, a jeśli jest jakiś problem, zdecydowanie chciałbym go naprawić. – dvntehn00bz

+1

"print" Błąd: proces trwa zbyt długo, aby zakończyć - zakończenie "" może zostać uruchomione, nawet jeśli jest to kłamstwo, a proces kończy się bez zabicia go (ponieważ robi to w momentach między połączeniami "poll" i "kill"). –

8

subprocess.Popen nie blokuje więc można zrobić coś takiego:

import time 

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

Ma wadę, że należy zawsze odczekać co najmniej 20 sekund na to, aby zakończyć.

+3

To spowodowałoby zatrzymanie tego procesu przez 20 sekund. Czy to jest do przyjęcia? –

+3

nie pokonuje tego rodzaju podprocesu? – aaronasterling

+0

wydaje się, że jest! Myślę, że to jest mała niedogodność w używaniu podprocesu. – sultan

2

Niestety, nie ma takiego rozwiązania. Udało mi się to zrobić za pomocą nagwintowanego timera, który uruchomiłbym wraz z procesem, który zabiłby go po przekroczeniu limitu czasu, ale wpadłem na kilka starych problemów deskryptorów plików z powodu procesów zombie lub niektórych takich.

+0

+1 To byłoby moje rozwiązanie. – aaronasterling

+1

Doświadczyłem też takiego problemu z deskryptorami plików w procesach zombie. – sultan

+0

Sultan. Powinno być możliwe ich skorygowanie. Udało mi się wreszcie wypolerować moją aplikację w coś praktycznego, ale nie była ona wystarczająco ogólna, aby ją opublikować. –

2

Nie ma wolnego czasu. Sądzę, że to, czego szukasz, to zabicie podprocesora po pewnym czasie. Ponieważ jesteś w stanie sygnalizować podproces, powinieneś być w stanie go zabić.

ogólne podejście do wysyłania sygnału do podproces:

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

Można użyć tego mechanizmu, aby zakończyć po pewnym czasie poza okres.

+0

Czy jest to uniwersalna sys.stdout.flush() dla wszystkich aplikacji korzystających z podprocesu? – sultan

+2

Jest to prawie jak odpowiedź Abhisheka. Używa SIGKILL i używasz SIGUSR1. Ma te same problemy. –

+0

OK dzięki chłopaki! – sultan

4

Można zrobić

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

użyciu skręconych jest procesem asynchronicznym API.

0

W systemie Linux można użyć sygnału. Jest to zależne od platformy, więc inne rozwiązanie jest wymagane w systemie Windows. Może działać z komputerem 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

pytona podproces auto-timeout nie jest wbudowane, więc masz zamiar zbudować własną.

Działa to na mnie działa na Ubuntu 12.10 Python 2.7.3

umieścić to w pliku o nazwie 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() 

zapisz go i uruchom go:

python test.py 

Komenda sleep 20 trwa 20 sekund. Jeśli nie zakończy się w ciągu 3 sekund (nie nastąpi), proces zostanie zakończony.

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

Między uruchomieniem procesu i trzema sekundami jest przerwany.

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) 

Wyjście to powinno być:

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

, gdzie można zauważyć, że w pierwszym wykonaniu, proces zakończeniu prawidłowo (kod powrotu 0), natomiast w drugim z proces został zakończony (kod powrotu -15).

Nie testowałem w systemie Windows; ale oprócz aktualizacji przykładowego polecenia, myślę, że powinno działać, ponieważ nie znalazłem w dokumentacji niczego, co mówi, że thread.join lub process.terminate nie jest obsługiwane.

1

Tak https://pypi.python.org/pypi/python-subprocess2 przedłuży moduł POPEN z dwoma dodatkowymi funkcjami,

Popen.waitUpTo(timeout=seconds) 

To będzie czekać aż do acertain liczbę sekund na zakończenie procesu, w przeciwnym razie zwraca None

także,

Popen.waitOrTerminate 

To będzie czekać aż do punktu, a następnie zadzwonić .terminate(), a następnie .kill(), jeden lub kilka innych Orthe połączenie obu, patrz Dokumentacja dla pełnych szczegółów:

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