Korzystanie z modułu „podproces” z limitem czasu

325

Oto kod Pythona, aby uruchomić dowolne polecenie zwracające jego stdoutdane lub zgłosić wyjątek od niezerowych kodów wyjścia:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # Merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate służy do oczekiwania na zakończenie procesu:

stdoutdata, stderrdata = proc.communicate()

subprocessModuł nie obsługuje limit czasu - zdolność do zabijania procesu uruchomionego przez więcej niż x liczbę sekund - w związku z tym communicatemoże mieć wiecznie uciekać.

Jaki jest najprostszy sposób na wdrożenie limitów czasu w programie Python przeznaczonym do uruchamiania w systemach Windows i Linux?

Sridhar Ratnakumar
źródło
2
Powiązany wpis narzędzia do śledzenia problemów w języku Python: bugs.python.org/issue5673
Sridhar Ratnakumar
10
Użyj pypi.python.org/pypi/subprocess32 dla Python2.x. Jest to backport Python 3.x. Ma argument limitu czasu dla call () i wait ().
guettli
1
pypi.python.org/pypi/subprocess32 nie działa w systemie Windows :(
adrianX

Odpowiedzi:

170

W Python 3.3+:

from subprocess import STDOUT, check_output

output = check_output(cmd, stderr=STDOUT, timeout=seconds)

output jest ciągiem bajtów, który zawiera połączone stdout polecenia, dane stderr.

check_outputpodnosi CalledProcessErrorniezerowy status wyjścia, jak określono w tekście pytania, w przeciwieństwie do proc.communicate()metody.

Usunąłem, shell=Trueponieważ często jest używany niepotrzebnie. Zawsze możesz go dodać z powrotem, jeśli cmdrzeczywiście tego wymaga. Jeśli dodasz shell=Truetj. Jeśli proces potomny spawnuje swoich potomków; check_output()może powrócić znacznie później niż wskazuje limit czasu, zobacz Błąd przekroczenia limitu czasu podprocesu .

Funkcja limitu czasu jest dostępna w Pythonie 2.x poprzez subprocess32backport modułu podprocesora 3.2+.

JFS
źródło
17
Rzeczywiście, istnieje wsparcie dla przekroczenia
gps
8
@gps Sridhar poprosił o rozwiązanie wieloplatformowe, podczas gdy twój backport obsługuje tylko POSIX: kiedy go wypróbowałem, MSVC narzekał (oczekiwał) na brak unistd.h :)
Shmil The Cat
Jeśli nie potrzebujesz danych wyjściowych, możesz po prostu użyć subprocess.call.
Kyle Gibson
Od wersji Python3.5 użyj subprocess.run () z capture_output = True i użyj parametru kodowania, aby uzyskać wyjście usefoul.
MKesper
1
@MKesper: 1- check_output()jest preferowanym sposobem uzyskania danych wyjściowych (zwraca dane wyjściowe bezpośrednio, nie ignoruje błędów, jest dostępne od zawsze). 2- run()jest bardziej elastyczny, ale run()domyślnie ignoruje błąd i wymaga dodatkowych kroków, aby uzyskać wynik 3- check_output()jest zaimplementowany pod względemrun() i dlatego przyjmuje większość takich samych argumentów. 4-nit: capture_outputjest dostępny od 3.7, a nie 3.5
jfs
205

Nie wiem dużo o szczegółach niskiego poziomu; ale biorąc pod uwagę, że w Pythonie 2.6 API oferuje możliwość czekania na wątki i kończenia procesów, co z uruchomieniem procesu w osobnym wątku?

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)

Dane wyjściowe tego fragmentu w mojej maszynie to:

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

gdzie widać, że w pierwszym wykonaniu proces zakończył się poprawnie (kod powrotu 0), podczas gdy w drugim 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.

jcollado
źródło
16
+1 Za niezależność od platformy. Uruchomiłem to zarówno na systemie Linux, jak i Windows 7 (cygwin i zwykły python w systemie Windows) - działa zgodnie z oczekiwaniami we wszystkich trzech przypadkach.
phooji
7
Zmodyfikowałem trochę twój kod, aby móc przekazywać rodzime kweny Popen i nadawać mu formę. Jest teraz gotowy do użycia; gist.github.com/1306188
kirpit
2
Dla każdego, kto miał problem @redice, to pytanie może pomóc. Krótko mówiąc, jeśli użyjesz shell = True, powłoka staje się procesem potomnym, który zostaje zabity, a jego polecenie (potomek procesu potomnego) trwa.
Anson,
6
Ta odpowiedź nie zapewnia takiej samej funkcjonalności jak oryginał, ponieważ nie zwraca standardowego wyjścia.
stephenbez
2
Thread.is_alive może prowadzić do wyścigu. Zobacz ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut
132

Odpowiedź jcollado można uprościć za pomocą klasy threading.Timer :

import shlex
from subprocess import Popen, PIPE
from threading import Timer

def run(cmd, timeout_sec):
    proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
    timer = Timer(timeout_sec, proc.kill)
    try:
        timer.start()
        stdout, stderr = proc.communicate()
    finally:
        timer.cancel()

# Examples: both take 1 second
run("sleep 1", 5)  # process ends normally at 1 second
run("sleep 5", 1)  # timeout happens at 1 second
sussudio
źródło
11
+1 za proste rozwiązanie przenośne. Nie potrzebujesz lambda:t = Timer(timeout, proc.kill)
jfs
3
+1 To powinna być zaakceptowana odpowiedź, ponieważ nie wymaga zmiany sposobu uruchomienia procesu.
Dave Branton
1
Dlaczego wymaga lambda? Czy nie można zastosować powiązanej metody p.kill bez lambda?
Danny Staple
// Czy zechciałbyś podać przykład użycia tego?
Nathan Basanese,
1
@tuk timer.isAlive()wcześniej timer.cancel()oznacza, że ​​zakończyło się normalnie
Charles
83

Jeśli korzystasz z Uniksa,

import signal
  ...
class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60)  # 5 minutes
try:
    stdoutdata, stderrdata = proc.communicate()
    signal.alarm(0)  # reset the alarm
except Alarm:
    print "Oops, taking too long!"
    # whatever else
Alex Martelli
źródło
3
Cóż, jestem zainteresowany rozwiązaniem międzyplatformowym, które działa przynajmniej na Win / Linux / Mac.
Sridhar Ratnakumar
1
Lubię to podejście oparte na Uniksie. Idealnie byłoby połączyć to z podejściem specyficznym dla systemu Windows (przy użyciu CreateProcess i Jobs) .. ale na razie poniższe rozwiązanie jest proste, łatwe i jak dotąd działa.
Sridhar Ratnakumar
3
Dodałem przenośne rozwiązanie, patrz moja odpowiedź
flybywire,
4
To rozwiązanie działałoby tylko wtedy, gdy sygnał.signal (signal.SIGALARM, alarm_handler) jest wywoływany z głównego wątku. Zobacz dokumentację dla sygnału
volatilevoid
Niestety, kiedy działam (na Linuksie) w kontekście modułu Apache (takiego jak mod_python, mod_perl lub mod_php), zauważyłem, że użycie sygnałów i alarmów jest niedozwolone (prawdopodobnie dlatego, że kolidują z własną logiką IPC Apache). Aby więc osiągnąć cel przekroczenia limitu czasu polecenia, byłem zmuszony napisać „pętle nadrzędne”, które uruchamiają proces potomny, a następnie usiąść w pętli „uśpionej” i obserwować zegar (i ewentualnie także monitorować wyjście od dziecka).
Peter
44

Oto rozwiązanie Alexa Martellego jako moduł z odpowiednim zabijaniem procesów. Inne podejścia nie działają, ponieważ nie używają proc.communicate (). Więc jeśli masz proces, który produkuje dużo danych wyjściowych, zapełni bufor wyjściowy, a następnie zablokuje, dopóki czegoś z niego nie przeczytasz.

from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen

def run(args, cwd = None, shell = False, kill_tree = True, timeout = -1, env = None):
    '''
    Run a command with a timeout after which it will be forcibly
    killed.
    '''
    class Alarm(Exception):
        pass
    def alarm_handler(signum, frame):
        raise Alarm
    p = Popen(args, shell = shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
    if timeout != -1:
        signal(SIGALRM, alarm_handler)
        alarm(timeout)
    try:
        stdout, stderr = p.communicate()
        if timeout != -1:
            alarm(0)
    except Alarm:
        pids = [p.pid]
        if kill_tree:
            pids.extend(get_process_children(p.pid))
        for pid in pids:
            # process might have died before getting to this line
            # so wrap to avoid OSError: no such process
            try: 
                kill(pid, SIGKILL)
            except OSError:
                pass
        return -9, '', ''
    return p.returncode, stdout, stderr

def get_process_children(pid):
    p = Popen('ps --no-headers -o pid --ppid %d' % pid, shell = True,
              stdout = PIPE, stderr = PIPE)
    stdout, stderr = p.communicate()
    return [int(p) for p in stdout.split()]

if __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)
Björn Lindqvist
źródło
3
To nie będzie działać w systemie Windows, a kolejność funkcji jest odwrócona.
Hamish Grubijan
3
Czasami powoduje to wyjątek, gdy inny moduł obsługi rejestruje się w SIGALARM i zabija proces, zanim ten „zabije”, dodano obejście. BTW, świetny przepis! Użyłem tego do uruchomienia 50 000 błędnych procesów do tej pory bez zamrażania lub zawieszania opakowania obsługi.
Yaroslav Bulatov
Jak można to zmodyfikować, aby działało w aplikacji wątkowej? Próbuję użyć go z wątków roboczych i uzyskaćValueError: signal only works in main thread
wim
@Yaroslav Bulatov Dzięki za informację. Jakie obejście dodałeś, aby rozwiązać wspomniany problem?
jpswain,
1
Właśnie dodałem blok „spróbuj; złap”, jest w kodzie. BTW, na dłuższą metę okazało się, że mam problemy, ponieważ można ustawić tylko jedną procedurę obsługi SIGALARM, a inne procesy mogą ją zresetować. Tutaj podano jedno rozwiązanie tego problemu - stackoverflow.com/questions/6553423/…
Jarosław Bułatow
18

Zmodyfikowałem odpowiedź sussudio . Teraz zwraca: ( returncode, stdout, stderr, timeout) - stdouti stderrjest dekodowany do UTF-8 ciąg

def kill_proc(proc, timeout):
  timeout["value"] = True
  proc.kill()

def run(cmd, timeout_sec):
  proc = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  timeout = {"value": False}
  timer = Timer(timeout_sec, kill_proc, [proc, timeout])
  timer.start()
  stdout, stderr = proc.communicate()
  timer.cancel()
  return proc.returncode, stdout.decode("utf-8"), stderr.decode("utf-8"), timeout["value"]
Michał Zmuda
źródło
18

zaskoczony, nikt nie wspomniał o użyciu timeout

timeout 5 ping -c 3 somehost

To oczywiście nie zadziała w każdym przypadku użycia, ale jeśli masz do czynienia z prostym skryptem, trudno go pokonać.

Dostępny również jako gtimeout w Coreutils przez homebrewdla użytkowników komputerów Mac.

Karsten
źródło
1
to znaczy: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Czy timeoutw systemie Windows jest polecenie, jak pyta OP?
jfs
W systemie Windows można używać aplikacji takich jak git bash, która pozwala na narzędzia bash w systemie Windows.
Kaushik Acharya
@KaushikAcharya, nawet jeśli używasz git bash, gdy Python wywoła podproces, będzie działał w systemie Windows, dlatego to obejście nie będzie działać.
Naman Chikara,
16

timeoutjest teraz obsługiwany przez call()i communicate()w module podprocesu (od Python3.3):

import subprocess

subprocess.call("command", timeout=20, shell=True)

Spowoduje to wywołanie polecenia i zgłoszenie wyjątku

subprocess.TimeoutExpired

jeśli polecenie nie zakończy się po 20 sekundach.

Następnie możesz obsłużyć wyjątek, aby kontynuować kod, na przykład:

try:
    subprocess.call("command", timeout=20, shell=True)
except subprocess.TimeoutExpired:
    # insert code here

Mam nadzieję że to pomoże.

James
źródło
istnieje odpowiedź, która wspomina o timeoutparametrze . Chociaż powtórzenie tego nie zaszkodzi.
jfs
// myślę, że OP szuka rozwiązania dla starszego Pythona.
Nathan Basanese
11

Inną opcją jest zapisanie do pliku tymczasowego, aby zapobiec blokowaniu standardowego wyjścia zamiast konieczności odpytywania za pomocą komunikacji (). To zadziałało dla mnie, gdy inne odpowiedzi nie; na przykład w systemie Windows.

    outFile =  tempfile.SpooledTemporaryFile() 
    errFile =   tempfile.SpooledTemporaryFile() 
    proc = subprocess.Popen(args, stderr=errFile, stdout=outFile, universal_newlines=False)
    wait_remaining_sec = timeout

    while proc.poll() is None and wait_remaining_sec > 0:
        time.sleep(1)
        wait_remaining_sec -= 1

    if wait_remaining_sec <= 0:
        killProc(proc.pid)
        raise ProcessIncompleteError(proc, timeout)

    # read temp streams from start
    outFile.seek(0);
    errFile.seek(0);
    out = outFile.read()
    err = errFile.read()
    outFile.close()
    errFile.close()
Matt
źródło
Wydaje się niekompletny - co to jest plik tymczasowy?
spiderplant0
Dołącz „import tempfile”, „import time” i „shell = True” w wywołaniu „Popen” (uważaj na „shell = True”)!
Eduardo Lucio
11

Nie wiem, dlaczego to nie wymieniono, ale ponieważ Python 3.5, nowe subprocess.rununiwersalne polecenia (które ma zastąpić check_call, check_output...) i który ma timeoutparametr, jak również.

subprocess.run (args, *, stdin = None, input = None, stdout = None, stderr = None, shell = False, cwd = None, timeout = None, check = False, kodowanie = None, błędy = None)

Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.

Zgłasza subprocess.TimeoutExpiredwyjątek, gdy upłynął limit czasu.

Jean-François Fabre
źródło
6

Oto moje rozwiązanie, korzystałem z wątku i zdarzenia:

import subprocess
from threading import Thread, Event

def kill_on_timeout(done, timeout, proc):
    if not done.wait(timeout):
        proc.kill()

def exec_command(command, timeout):

    done = Event()
    proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    watcher = Thread(target=kill_on_timeout, args=(done, timeout, proc))
    watcher.daemon = True
    watcher.start()

    data, stderr = proc.communicate()
    done.set()

    return data, stderr, proc.returncode

W akcji:

In [2]: exec_command(['sleep', '10'], 5)
Out[2]: ('', '', -9)

In [3]: exec_command(['sleep', '10'], 11)
Out[3]: ('', '', 0)
rsk
źródło
5

Rozwiązaniem, którego używam, jest poprzedzenie polecenia powłoki limitem czasowym . Jeśli polecenie trwa zbyt długo, limit czasu go zatrzyma, a Popen będzie miał ustawiony kod powrotu ustawiony przez timelimit. Jeśli jest> 128, oznacza to, że limit czasu zabił proces.

Zobacz także podprocesor Pythona z limitem czasu i dużą wydajnością (> 64 KB)

bortzmeyer
źródło
Korzystam z podobnego narzędzia o nazwie timeout- packages.ubuntu.com/search?ke words=timeout - ale żadne z nich nie działa w systemie Windows, prawda?
Sridhar Ratnakumar,
5

Dodałem rozwiązanie z wątkami od jcolladodo łatwego procesu modułu Pythona .

Zainstalować:

pip install easyprocess

Przykład:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout
ponty
źródło
Moduł easyprocess ( code.activestate.com/pypm/easyprocess ) działał dla mnie, nawet używając go z wielu procesów.
iChux
5

jeśli używasz Pythona 2, spróbuj

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e
Thong Long
źródło
1
Prawdopodobnie nie działa w systemie Windows, zgodnie z pytaniem wstępnym
Jean-Francois T.,
5

Przygotowanie polecenia Linux timeoutnie jest złym obejściem i działało dla mnie.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
Vikram Hosakote
źródło
Jak mogę wydrukować ciągi wyjścia umieszczonego podczas wykonywania podprocesu? - Wiadomości wysyłane są zwracane przez podproces.
Ammad
3

Zaimplementowałem to, co mogłem zebrać z kilku z nich. Działa to w systemie Windows, a ponieważ jest to wiki społeczności, myślę, że udostępnię również mój kod:

class Command(threading.Thread):
    def __init__(self, cmd, outFile, errFile, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd
        self.process = None
        self.outFile = outFile
        self.errFile = errFile
        self.timed_out = False
        self.timeout = timeout

    def run(self):
        self.process = subprocess.Popen(self.cmd, stdout = self.outFile, \
            stderr = self.errFile)

        while (self.process.poll() is None and self.timeout > 0):
            time.sleep(1)
            self.timeout -= 1

        if not self.timeout > 0:
            self.process.terminate()
            self.timed_out = True
        else:
            self.timed_out = False

Następnie z innej klasy lub pliku:

        outFile =  tempfile.SpooledTemporaryFile()
        errFile =   tempfile.SpooledTemporaryFile()

        executor = command.Command(c, outFile, errFile, timeout)
        executor.daemon = True
        executor.start()

        executor.join()
        if executor.timed_out:
            out = 'timed out'
        else:
            outFile.seek(0)
            errFile.seek(0)
            out = outFile.read()
            err = errFile.read()

        outFile.close()
        errFile.close()
joslinm
źródło
Właściwie to prawdopodobnie nie działa. Te terminate()znaki funkcyjne wątek jako zakończone, ale faktycznie nie zakończyć wątek! Mogę to sprawdzić w * nix, ale nie mam komputera z systemem Windows do przetestowania.
dotancohen
2

Gdy zrozumiesz pełną maszynę do obsługi procesów w * unix, z łatwością znajdziesz prostsze rozwiązanie:

Rozważ ten prosty przykład, w jaki sposób ustawić limit czasu komunikacji () za pomocą metody select.select () (obecnie dostępnej prawie wszędzie w * nix). Można to również napisać za pomocą epoll / poll / kqueue, ale wariant select.select () może być dla Ciebie dobrym przykładem. Główne ograniczenia select.select () (szybkość i maks. 1024 FDS) nie mają zastosowania do twojego zadania.

Działa to pod * nix, nie tworzy wątków, nie używa sygnałów, może być wyrywane z dowolnego wątku (nie tylko głównego) i wystarczająco szybkie, aby odczytać 250 Mb / s danych ze standardowego wyjścia na moim komputerze (i5 2,3 GHz).

Wystąpił problem z dołączeniem stdout / stderr na końcu komunikacji. Jeśli masz duży program wyjściowy, może to prowadzić do dużego zużycia pamięci. Ale możesz wywołać komunikację () kilka razy z mniejszymi limitami czasu.

class Popen(subprocess.Popen):
    def communicate(self, input=None, timeout=None):
        if timeout is None:
            return subprocess.Popen.communicate(self, input)

        if self.stdin:
            # Flush stdio buffer, this might block if user
            # has been writing to .stdin in an uncontrolled
            # fashion.
            self.stdin.flush()
            if not input:
                self.stdin.close()

        read_set, write_set = [], []
        stdout = stderr = None

        if self.stdin and input:
            write_set.append(self.stdin)
        if self.stdout:
            read_set.append(self.stdout)
            stdout = []
        if self.stderr:
            read_set.append(self.stderr)
            stderr = []

        input_offset = 0
        deadline = time.time() + timeout

        while read_set or write_set:
            try:
                rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
            except select.error as ex:
                if ex.args[0] == errno.EINTR:
                    continue
                raise

            if not (rlist or wlist):
                # Just break if timeout
                # Since we do not close stdout/stderr/stdin, we can call
                # communicate() several times reading data by smaller pieces.
                break

            if self.stdin in wlist:
                chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
                try:
                    bytes_written = os.write(self.stdin.fileno(), chunk)
                except OSError as ex:
                    if ex.errno == errno.EPIPE:
                        self.stdin.close()
                        write_set.remove(self.stdin)
                    else:
                        raise
                else:
                    input_offset += bytes_written
                    if input_offset >= len(input):
                        self.stdin.close()
                        write_set.remove(self.stdin)

            # Read stdout / stderr by 1024 bytes
            for fn, tgt in (
                (self.stdout, stdout),
                (self.stderr, stderr),
            ):
                if fn in rlist:
                    data = os.read(fn.fileno(), 1024)
                    if data == '':
                        fn.close()
                        read_set.remove(fn)
                    tgt.append(data)

        if stdout is not None:
            stdout = ''.join(stdout)
        if stderr is not None:
            stderr = ''.join(stderr)

        return (stdout, stderr)
Vadim Fint
źródło
2
To rozwiązuje tylko połowę problemu w Uniksie.
Spaceghost
2

Możesz to zrobić za pomocą select

import subprocess
from datetime import datetime
from select import select

def call_with_timeout(cmd, timeout):
    started = datetime.now()
    sp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    while True:
        p = select([sp.stdout], [], [], timeout)
        if p[0]:
            p[0][0].read()
        ret = sp.poll()
        if ret is not None:
            return ret
        if (datetime.now()-started).total_seconds() > timeout:
            sp.kill()
            return None
dspeyer
źródło
1

Z powodzeniem korzystałem z procesu killableprocess w systemach Windows, Linux i Mac. Jeśli używasz Cygwin Python, będziesz potrzebować wersji killableprocess OSAF, ponieważ w przeciwnym razie natywne procesy systemu Windows nie zostaną zabite.

Heikki Toivonen
źródło
Wygląda na to, że killableprocess nie dodaje limitu czasu do wywołania Popen.communicate ().
Wim Coenen,
1

Chociaż nie przyglądałem się temu dokładnie, ten dekorator, który znalazłem w ActiveState, wydaje się być całkiem przydatny do tego rodzaju rzeczy. Wraz z subprocess.Popen(..., close_fds=True)przynajmniej jestem gotowy do skryptowania powłoki w Pythonie.

Ehtesh Choudhury
źródło
Ten dekorator używa sygnału.alarm, który nie jest dostępny w systemie Windows.
dbn
1

To rozwiązanie zabija drzewo procesów w przypadku powłoki = True, przekazuje parametry do procesu (lub nie), ma limit czasu i pobiera wyjście standardowe, standardowe i wyjściowe wywołania zwrotnego (używa psutil dla drzewa_zabicia). Opierało się to na kilku rozwiązaniach opublikowanych w SO, w tym na jcollado. Publikowanie w odpowiedzi na komentarze Ansona i jradice w odpowiedzi jcollado. Testowane w Windows Srvr 2012 i Ubuntu 14.04. Pamiętaj, że w Ubuntu musisz zmienić wywołanie parent.children (...) na parent.get_children (...).

def kill_proc_tree(pid, including_parent=True):
  parent = psutil.Process(pid)
  children = parent.children(recursive=True)
  for child in children:
    child.kill()
  psutil.wait_procs(children, timeout=5)
  if including_parent:
    parent.kill()
    parent.wait(5)

def run_with_timeout(cmd, current_dir, cmd_parms, timeout):
  def target():
    process = subprocess.Popen(cmd, cwd=current_dir, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

    # wait for the process to terminate
    if (cmd_parms == ""):
      out, err = process.communicate()
    else:
      out, err = process.communicate(cmd_parms)
    errcode = process.returncode

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

  thread.join(timeout)
  if thread.is_alive():
    me = os.getpid()
    kill_proc_tree(me, including_parent=False)
    thread.join()
Tomas
źródło
1

Istnieje pomysł, aby podklasować klasę Popen i rozszerzyć ją o kilka prostych dekoratorów metod. Nazwijmy to ExpiablePopen.

from logging import error
from subprocess import Popen
from threading import Event
from threading import Thread


class ExpirablePopen(Popen):

    def __init__(self, *args, **kwargs):
        self.timeout = kwargs.pop('timeout', 0)
        self.timer = None
        self.done = Event()

        Popen.__init__(self, *args, **kwargs)

    def __tkill(self):
        timeout = self.timeout
        if not self.done.wait(timeout):
            error('Terminating process {} by timeout of {} secs.'.format(self.pid, timeout))
            self.kill()

    def expirable(func):
        def wrapper(self, *args, **kwargs):
            # zero timeout means call of parent method
            if self.timeout == 0:
                return func(self, *args, **kwargs)

            # if timer is None, need to start it
            if self.timer is None:
                self.timer = thr = Thread(target=self.__tkill)
                thr.daemon = True
                thr.start()

            result = func(self, *args, **kwargs)
            self.done.set()

            return result
        return wrapper

    wait = expirable(Popen.wait)
    communicate = expirable(Popen.communicate)


if __name__ == '__main__':
    from subprocess import PIPE

    print ExpirablePopen('ssh -T [email protected]', stdout=PIPE, timeout=1).communicate()
Alexander Yakovlev
źródło
1

Miałem problem polegający na tym, że chciałem zakończyć podproces wielowątkowy, jeśli trwa to dłużej niż określony limit czasu. Chciałem ustawić limit czasu Popen(), ale to nie zadziałało. Potem zdałem sobie sprawę, że Popen().wait()to jest równe call()i dlatego wpadłem na pomysł, aby ustawić limit czasu w .wait(timeout=xxx)metodzie, która w końcu zadziałała. W ten sposób rozwiązałem to w ten sposób:

import os
import sys
import signal
import subprocess
from multiprocessing import Pool

cores_for_parallelization = 4
timeout_time = 15  # seconds

def main():
    jobs = [...YOUR_JOB_LIST...]
    with Pool(cores_for_parallelization) as p:
        p.map(run_parallel_jobs, jobs)

def run_parallel_jobs(args):
    # Define the arguments including the paths
    initial_terminal_command = 'C:\\Python34\\python.exe'  # Python executable
    function_to_start = 'C:\\temp\\xyz.py'  # The multithreading script
    final_list = [initial_terminal_command, function_to_start]
    final_list.extend(args)

    # Start the subprocess and determine the process PID
    subp = subprocess.Popen(final_list)  # starts the process
    pid = subp.pid

    # Wait until the return code returns from the function by considering the timeout. 
    # If not, terminate the process.
    try:
        returncode = subp.wait(timeout=timeout_time)  # should be zero if accomplished
    except subprocess.TimeoutExpired:
        # Distinguish between Linux and Windows and terminate the process if 
        # the timeout has been expired
        if sys.platform == 'linux2':
            os.kill(pid, signal.SIGTERM)
        elif sys.platform == 'win32':
            subp.terminate()

if __name__ == '__main__':
    main()
sjor
źródło
0

Niestety obowiązują mnie bardzo surowe zasady dotyczące ujawniania kodu źródłowego przez mojego pracodawcę, więc nie mogę podać rzeczywistego kodu. Ale moim zdaniem najlepszym rozwiązaniem jest utworzenie podklasy zastępującej Popen.wait()ankietę zamiast czekać w nieskończoność i Popen.__init__zaakceptowanie parametru limitu czasu. Po wykonaniu tej czynności wszystkie pozostałe Popenmetody (które wywołanie wait) będą działać zgodnie z oczekiwaniami, w tym communicate.

Patrick Narkinsky
źródło
0

https://pypi.python.org/pypi/python-subprocess2 zapewnia rozszerzenia modułu podprocesu, które pozwalają poczekać do pewnego okresu czasu, w przeciwnym razie zakończyć.

Więc poczekaj 10 sekund na zakończenie procesu, w przeciwnym razie zabij:

pipe  = subprocess.Popen('...')

timeout =  10

results = pipe.waitOrTerminate(timeout)

Jest to zgodne zarówno z systemem Windows, jak i Unix. „results” jest słownikiem, zawiera „returnCode”, który jest zwrotem aplikacji (lub None, jeśli trzeba ją zabić), a także „actionTaken”. który będzie „SUBPROCESS2_PROCESS_COMPLETED”, jeśli proces zakończy się normalnie, lub maska ​​„SUBPROCESS2_PROCESS_TERMINATED” i SUBPROCESS2_PROCESS_KILLED w zależności od podjętych działań (szczegółowe informacje znajdują się w dokumentacji)

Tim Savannah
źródło
0

dla Pythona 2.6+ użyj gevent

 from gevent.subprocess import Popen, PIPE, STDOUT

 def call_sys(cmd, timeout):
      p= Popen(cmd, shell=True, stdout=PIPE)
      output, _ = p.communicate(timeout=timeout)
      assert p.returncode == 0, p. returncode
      return output

 call_sys('./t.sh', 2)

 # t.sh example
 sleep 5
 echo done
 exit 1
whi
źródło
0

python 2.7

import time
import subprocess

def run_command(cmd, timeout=0):
    start_time = time.time()
    df = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    while timeout and df.poll() == None:
        if time.time()-start_time >= timeout:
            df.kill()
            return -1, ""
    output = '\n'.join(df.communicate()).strip()
    return df.returncode, output
Joy Wang
źródło
-1
import subprocess, optparse, os, sys, re, datetime, threading, time, glob, shutil, xml.dom.minidom, traceback

class OutputManager:
    def __init__(self, filename, mode, console, logonly):
        self.con = console
        self.logtoconsole = True
        self.logtofile = False

        if filename:
            try:
                self.f = open(filename, mode)
                self.logtofile = True
                if logonly == True:
                    self.logtoconsole = False
            except IOError:
                print (sys.exc_value)
                print ("Switching to console only output...\n")
                self.logtofile = False
                self.logtoconsole = True

    def write(self, data):
        if self.logtoconsole == True:
            self.con.write(data)
        if self.logtofile == True:
            self.f.write(data)
        sys.stdout.flush()

def getTimeString():
        return time.strftime("%Y-%m-%d", time.gmtime())

def runCommand(command):
    '''
    Execute a command in new thread and return the
    stdout and stderr content of it.
    '''
    try:
        Output = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]
    except Exception as e:
        print ("runCommand failed :%s" % (command))
        print (str(e))
        sys.stdout.flush()
        return None
    return Output

def GetOs():
    Os = ""
    if sys.platform.startswith('win32'):
        Os = "win"
    elif sys.platform.startswith('linux'):
        Os = "linux"
    elif sys.platform.startswith('darwin'):
        Os = "mac"
    return Os


def check_output(*popenargs, **kwargs):
    try:
        if 'stdout' in kwargs: 
            raise ValueError('stdout argument not allowed, it will be overridden.') 

        # Get start time.
        startTime = datetime.datetime.now()
        timeoutValue=3600

        cmd = popenargs[0]

        if sys.platform.startswith('win32'):
            process = subprocess.Popen( cmd, stdout=subprocess.PIPE, shell=True) 
        elif sys.platform.startswith('linux'):
            process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True ) 
        elif sys.platform.startswith('darwin'):
            process = subprocess.Popen( cmd , stdout=subprocess.PIPE, shell=True ) 

        stdoutdata, stderrdata = process.communicate( timeout = timeoutValue )
        retcode = process.poll()

        ####################################
        # Catch crash error and log it.
        ####################################
        OutputHandle = None
        try:
            if retcode >= 1:
                OutputHandle = OutputManager( 'CrashJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
                OutputHandle.write( cmd )
                print (stdoutdata)
                print (stderrdata)
                sys.stdout.flush()
        except Exception as e:
            print (str(e))

    except subprocess.TimeoutExpired:
            ####################################
            # Catch time out error and log it.
            ####################################
            Os = GetOs()
            if Os == 'win':
                killCmd = "taskkill /FI \"IMAGENAME eq {0}\" /T /F"
            elif Os == 'linux':
                killCmd = "pkill {0)"
            elif Os == 'mac':
                # Linux, Mac OS
                killCmd = "killall -KILL {0}"

            runCommand(killCmd.format("java"))
            runCommand(killCmd.format("YouApp"))

            OutputHandle = None
            try:
                OutputHandle = OutputManager( 'KillJob_' + getTimeString() + '.txt', 'a+', sys.stdout, False)
                OutputHandle.write( cmd )
            except Exception as e:
                print (str(e))
    except Exception as e:
            for frame in traceback.extract_tb(sys.exc_info()[2]):
                        fname,lineno,fn,text = frame
                        print "Error in %s on line %d" % (fname, lineno)
PeterZhao
źródło
to jest obrzydliwość
Corey Goldberg
-2

Chciałem tylko napisać coś prostszego.

#!/usr/bin/python

from subprocess import Popen, PIPE
import datetime
import time 

popen = Popen(["/bin/sleep", "10"]);
pid = popen.pid
sttime = time.time();
waittime =  3

print "Start time %s"%(sttime)

while True:
    popen.poll();
    time.sleep(1)
    rcode = popen.returncode
    now = time.time();
    if [ rcode is None ]  and  [ now > (sttime + waittime) ] :
        print "Killing it now"
        popen.kill()
Jabir Ahmed
źródło
time.sleep (1) to bardzo zły pomysł - wyobraź sobie, że chcesz uruchomić wiele poleceń, które zajęłyby około 0,002 s. Powinieneś raczej poczekać, aż poll () (zobacz wybierz, dla Linuxa epol zalecane :)
ddzialak