Utilizzo del modulo "sottoprocesso" con timeout


325

Ecco il codice Python per eseguire un comando arbitrario che restituisce i suoi stdoutdati o sollevare un'eccezione su codici di uscita diversi da zero:

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

communicate viene utilizzato per attendere la chiusura del processo:

stdoutdata, stderrdata = proc.communicate()

Il subprocessmodulo non supporta il timeout - la capacità di terminare un processo in esecuzione per più di X numero di secondi - pertanto, communicatepotrebbe richiedere del tempo per l'esecuzione.

Qual è il modo più semplice per implementare i timeout in un programma Python pensato per essere eseguito su Windows e Linux?


2
Una voce relativa al tracker del problema di Python: bugs.python.org/issue5673
Sridhar Ratnakumar,

10
Utilizzare pypi.python.org/pypi/subprocess32 per Python2.x. È un backport di Python 3.x. Ha l'argomento timeout per call () e wait ().
Guettli,

1
pypi.python.org/pypi/subprocess32 non funziona su Windows :(
adrianX

Risposte:


170

In Python 3.3+:

from subprocess import STDOUT, check_output

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

output è una stringa di byte che contiene i dati stdout e stderr uniti nel comando.

check_outputgenera CalledProcessErroruno stato di uscita diverso da zero come specificato nel testo della domanda a differenza del proc.communicate()metodo.

L'ho rimosso shell=Trueperché spesso viene utilizzato inutilmente. Puoi sempre aggiungerlo nuovamente se cmdeffettivamente lo richiede. Se aggiungi shell=Truecioè, se il processo figlio genera i propri discendenti; check_output()può restituire molto più tardi di quanto indicato dal timeout, consultare Errore di timeout del sottoprocesso .

La funzione di timeout è disponibile su Python 2.x tramite il subprocess32backport del modulo di sottoprocesso 3.2+.


17
In effetti, e il supporto del timeout del sottoprocesso esiste nel backport subprocess32 che mantengo per l'uso su Python 2. pypi.python.org/pypi/subprocess32
gps

8
@gps Sridhar ha chiesto una soluzione multipiattaforma, mentre il tuo backport supporta solo POSIX: quando l'ho provato, MSVC si è lamentato (previsto) della mancanza di unistd.h :)
Shmil The Cat

Se non hai bisogno dell'output, puoi semplicemente usare subprocess.call.
Kyle Gibson,

Da Python3.5, utilizzare subprocess.run () con capture_output = True e utilizzare il parametro di codifica per ottenere l'outputfoulul.
MKesper,

1
@MKesper: 1- check_output()è il modo preferito per ottenere l'output (restituisce direttamente l'output, non ignora gli errori, è disponibile da sempre). 2- run()è più flessibile ma run()ignora l'errore per impostazione predefinita e richiede passaggi aggiuntivi per ottenere l'output 3- check_output()è implementato in termini dirun() e quindi accetta la maggior parte degli stessi argomenti. 4- nit: capture_outputè disponibile dal 3.7, non 3.5
jfs

205

Non so molto sui dettagli di basso livello; ma, dato che in Python 2.6 l'API offre la possibilità di attendere thread e terminare i processi, che dire dell'esecuzione del processo in un thread separato?

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)

L'output di questo frammento nella mia macchina è:

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

dove si può vedere che, nella prima esecuzione, il processo è terminato correttamente (codice di ritorno 0), mentre nella seconda il processo è stato terminato (codice di ritorno -15).

Non ho testato su Windows; ma, a parte l'aggiornamento del comando di esempio, penso che dovrebbe funzionare poiché non ho trovato nella documentazione nulla che dica che thread.join o process.terminate non sono supportati.


16
+1 Per essere indipendente dalla piattaforma. Ho eseguito questo su Linux e Windows 7 (Cygwin e Plain Windows Python) - funziona come previsto in tutti e tre i casi.
phooji,

7
Ho modificato un po 'il tuo codice per poter passare i kwarg Popen nativi e metterlo in pratica. Ora è pronto per l'uso multiuso; gist.github.com/1306188
kirpit

2
Per chiunque abbia il problema riscontrato da @redice, questa domanda può essere d'aiuto. In breve, se usi shell = True, la shell diventa il processo figlio che viene ucciso e il suo comando (figlio del processo figlio) sopravvive.
Anson

6
Questa risposta non fornisce la stessa funzionalità dell'originale poiché non restituisce stdout.
stephenbez,

2
thread.is_alive può portare a una condizione di competizione. Vedi ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut

132

La risposta di jcollado può essere semplificata usando la classe 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

11
+1 per una soluzione portatile semplice. Non hai bisogno di lambda:t = Timer(timeout, proc.kill)
jfs

3
+1 Questa dovrebbe essere la risposta accettata, perché non richiede il modo in cui il processo viene avviato per essere modificato.
Dave Branton,

1
Perché richiede la lambda? Il metodo associato p.kill non potrebbe essere utilizzato senza lambda lì?
Danny Staple

//, saresti disposto a includere un esempio dell'uso di questo?
Nathan Basanese,

1
@tuk timer.isAlive()prima timer.cancel()significa che è finita normalmente
Charles

83

Se sei su Unix,

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

3
Bene, sono interessato a una soluzione multipiattaforma che funziona almeno su win / linux / mac.
Sridhar Ratnakumar,

1
Mi piace questo approccio basato su unix. Idealmente, si dovrebbe combinare questo con un approccio specifico di Windows (utilizzando CreateProcess e Jobs) .. ma per ora, la soluzione di seguito è semplice, facile e funziona finora.
Sridhar Ratnakumar,

3
Ho aggiunto una soluzione portatile, vedi la mia risposta
flybywire,

4
Questa soluzione funzionerebbe solo_se signal.signal (signal.SIGALARM, alarm_handler) viene chiamato dal thread principale. Vedere la documentazione per signal
volatilevoid

Sfortunatamente, quando sono in esecuzione (su Linux) nel contesto di un modulo Apache (come mod_python, mod_perl o mod_php), ho trovato vietato l'uso di segnali e allarmi (presumibilmente perché interferiscono con la logica IPC di Apache). Quindi, per raggiungere l'obiettivo del timeout di un comando, sono stato costretto a scrivere "loop parent" che avviano un processo figlio e quindi siedono in un ciclo "sleep" e guardano l'orologio (e possibilmente anche il monitoraggio dell'output dal bambino).
Peter,

44

Ecco la soluzione di Alex Martelli come modulo con un'adeguata uccisione del processo. Gli altri approcci non funzionano perché non utilizzano proc.communicate (). Quindi, se hai un processo che produce molto output, riempirà il suo buffer di output e poi si bloccherà fino a quando non leggerai qualcosa da esso.

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)

3
Questo non funzionerà su Windows, inoltre l'ordine delle funzioni è invertito.
Hamish Grubijan,

3
Questo a volte si traduce in un'eccezione quando un altro gestore si registra su SIGALARM e termina il processo prima che questo possa "uccidere", ha aggiunto una soluzione. A proposito, ottima ricetta! Ho usato questo per avviare fino a 50.000 processi con errori senza congelare o arrestare il wrapper di gestione.
Yaroslav Bulatov,

Come può essere modificato per essere eseguito in un'applicazione Threaded? Sto cercando di usarlo dai thread di lavoro e ottenereValueError: signal only works in main thread
wim

@Yaroslav Bulatov Grazie per le informazioni. Qual è stata la soluzione alternativa che hai aggiunto per affrontare il problema menzionato?
jpswain,

1
Ho appena aggiunto il blocco "try; catch", è all'interno del codice. A proposito, a lungo termine, questo mi ha dato problemi perché puoi impostare un solo gestore SIGALARM e altri processi possono resettarlo. Una soluzione a questo è data qui - stackoverflow.com/questions/6553423/…
Yaroslav Bulatov

18

Ho modificato la risposta sussudio . Ora funzione ritorna: ( returncode, stdout, stderr, timeout) - stdouted stderrè decodificato a stringa UTF-8

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"]

18

sorpreso nessuno menzionato usando timeout

timeout 5 ping -c 3 somehost

Questo non funzionerà ovviamente per ogni caso d'uso, ma se hai a che fare con uno script semplice, è difficile da battere.

Disponibile anche come gtimeout in coreutils tramite homebrewper utenti mac.


1
cercavi: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). Esiste un timeoutcomando su Windows quando viene richiesto OP?
jfs,

In Windows, è possibile utilizzare un'applicazione come git bash che consente le utilità bash in Windows.
Kaushik Acharya,

@KaushikAcharya anche se usi git bash, quando python chiama sottoprocesso funzionerà su Windows, quindi questo bypass non funzionerà.
Naman Chikara,

16

timeoutè ora supportato da call()e communicate()nel modulo di sottoprocesso (a partire da Python3.3):

import subprocess

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

Questo chiamerà il comando e solleverà l'eccezione

subprocess.TimeoutExpired

se il comando non termina dopo 20 secondi.

È quindi possibile gestire l'eccezione per continuare il codice, ad esempio:

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

Spero che questo ti aiuti.


esiste una risposta esistente che menziona il timeoutparametro . Anche se menzionarlo ancora una volta non farebbe male.
jfs,

//, penso che OP stia cercando una soluzione per il vecchio Python.
Nathan Basanese,

11

Un'altra opzione è scrivere in un file temporaneo per impedire il blocco stdout invece di dover eseguire il polling con communic (). Questo ha funzionato per me, mentre le altre risposte no; ad esempio su 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()

Sembra incompleto: cos'è il tempfile?
spiderplant0

Includi "import tempfile", "import time" e "shell = True" nella chiamata "Popen" (fai attenzione con "shell = True")!
Eduardo Lucio,

11

Non so perché non sia menzionato, ma da Python 3.5 ce n'è uno nuovo subprocess.run comando universale (che è destinato a sostituire check_call, check_output...) e che ha anche il timeoutparametro.

subprocess.run (args, *, stdin = None, input = None, stdout = None, stderr = None, shell = False, cwd = None, timeout = None, check = False, encoding = None, errori = None)

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

Solleva subprocess.TimeoutExpiredun'eccezione quando il timeout è scaduto.


6

Ecco la mia soluzione, stavo usando Thread ed Event:

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

In azione:

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

In [3]: exec_command(['sleep', '10'], 11)
Out[3]: ('', '', 0)

5

La soluzione che uso è quella di aggiungere il prefisso al comando shell con timelimit . Se il comando impiega troppo tempo, timelimit lo fermerà e Popen avrà un codice di ritorno impostato da timelimit. Se è> 128, significa che timelimit ha interrotto il processo.

Vedi anche sottoprocesso di Python con timeout e output di grandi dimensioni (> 64 KB)


Uso uno strumento simile chiamato timeout- package.ubuntu.com/search?keywords=timeout - ma nessuno dei due funziona su Windows, vero?
Sridhar Ratnakumar,

5

Ho aggiunto la soluzione con il threading dal jcolladomio modulo Python easyprocess .

Installare:

pip install easyprocess

Esempio:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout

Il modulo easyprocess ( code.activestate.com/pypm/easyprocess ) ha funzionato per me, anche usandolo dal multiprocessing.
iChux

5

se stai usando Python 2, provalo

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e

1
Probabilmente non funziona su Windows, come richiesto nella domanda iniziale
Jean-Francois T.

5

Preparare il comando Linux timeoutnon è una brutta soluzione e ha funzionato per me.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()

Come posso stampare le stringhe put put durante l'esecuzione del processo secondario? - I messaggi in uscita vengono restituiti dal processo secondario.
Ammad,

3

Ho implementato ciò che avrei potuto raccogliere da alcuni di questi. Funziona in Windows, e poiché si tratta di un wiki della comunità, immagino che condividerei anche il mio codice:

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

Quindi da un'altra classe o file:

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

In realtà, questo probabilmente non funziona. La terminate()funzione contrassegna un thread come terminato, ma in realtà non termina il thread! Posso verificarlo in * nix, ma non ho un computer Windows su cui testare.
dotancohen,

2

Una volta compreso il processo completo di macchine in * unix, troverai facilmente una soluzione più semplice:

Considera questo semplice esempio come rendere timeoutable communic () meth usando select.select () (disponibile alsmost ovunque su * nix al giorno d'oggi). Questo può anche essere scritto con epoll / poll / kqueue, ma la variante select.select () potrebbe essere un buon esempio per te. E le principali limitazioni di select.select () (velocità e 1024 max fds) non sono applicabili alla tua attività.

Funziona con * nix, non crea thread, non utilizza segnali, può essere lanciato da qualsiasi thread (non solo principale) e abbastanza veloce da leggere 250mb / s di dati da stdout sulla mia macchina (i5 2.3ghz).

Si è verificato un problema durante l'unione di stdout / stderr alla fine della comunicazione. Se si dispone di un enorme output di programma, ciò potrebbe comportare un grande utilizzo della memoria. Ma puoi chiamare communic () più volte con timeout più piccoli.

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)

2
Questo risolve solo la metà del problema con Unix.
Spaceghost

2

Puoi farlo usando 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


1

Anche se non l'ho esaminato ampiamente, questo decoratore che ho trovato in ActiveState sembra essere abbastanza utile per questo genere di cose. Insieme subprocess.Popen(..., close_fds=True), almeno sono pronto per lo shell-scripting in Python.


Questo decoratore utilizza signal.alarm, che non è disponibile su Windows.
dbn

1

Questa soluzione uccide l'albero dei processi in caso di shell = True, passa i parametri al processo (o no), ha un timeout e ottiene l'output stdout, stderr e process della callback (usa psutil per kill_proc_tree). Questo si basava su diverse soluzioni pubblicate in SO, tra cui jcollado. Pubblicazione in risposta ai commenti di Anson e jradice nella risposta di jcollado. Testato in Windows Srvr 2012 e Ubuntu 14.04. Si noti che per Ubuntu è necessario modificare la chiamata parent.children (...) in 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()

1

C'è un'idea per sottoclassare la classe Popen ed estenderla con alcuni semplici decoratori di metodi. Chiamiamolo ExpirablePopen.

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 git@bitbucket.org', stdout=PIPE, timeout=1).communicate()

1

Ho avuto il problema che volevo terminare un sottoprocesso multithreading se impiegava più tempo di una determinata durata di timeout. Volevo impostare un timeout Popen(), ma non ha funzionato. Quindi, mi sono reso conto che Popen().wait()è uguale call()e quindi ho avuto l'idea di impostare un timeout all'interno del .wait(timeout=xxx)metodo, che alla fine ha funzionato. Quindi, l'ho risolto in questo modo:

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

0

Sfortunatamente, sono vincolato da politiche molto rigide sulla divulgazione del codice sorgente da parte del mio datore di lavoro, quindi non posso fornire il codice effettivo. Ma per i miei gusti la soluzione migliore è quella di creare una sottoclasse che ignori il Popen.wait()polling invece di attendere indefinitamente e Popen.__init__di accettare un parametro di timeout. Una volta che lo fai, tutti gli altri Popenmetodi (che chiamano wait) funzioneranno come previsto, incluso communicate.


0

https://pypi.python.org/pypi/python-subprocess2 fornisce estensioni al modulo sottoprocesso che consentono di attendere fino a un certo periodo di tempo, altrimenti terminare.

Quindi, attendere fino a 10 secondi per terminare il processo, altrimenti uccidere:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Questo è compatibile con Windows e Unix. "results" è un dizionario, contiene "returnCode" che è il ritorno dell'app (o None se dovesse essere ucciso), così come "actionTaken". che sarà "SUBPROCESS2_PROCESS_COMPLETED" se il processo è stato completato normalmente o una maschera di "SUBPROCESS2_PROCESS_TERMINATED" e SUBPROCESS2_PROCESS_KILLED a seconda dell'azione intrapresa (vedere la documentazione per tutti i dettagli)


0

per Python 2.6+, usa 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

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

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

questo è un abominio
Corey Goldberg,

-2

Stavo solo cercando di scrivere qualcosa di più semplice.

#!/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()

time.sleep (1) è una pessima idea: immagina di voler eseguire molti comandi che richiederebbero circa 0,002 secondi. Dovresti piuttosto aspettare mentre poll () (vedi select, per Linux epol consigliato :)
ddzialak
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.