Uso del módulo 'subproceso' con tiempo de espera

325

Aquí está el código de Python para ejecutar un comando arbitrario que devuelve sus stdoutdatos, o generar una excepción en los códigos de salida distintos de cero:

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

communicate se usa para esperar a que salga el proceso:

stdoutdata, stderrdata = proc.communicate()

El subprocessmódulo no admite el tiempo de espera (la capacidad de eliminar un proceso que se ejecuta durante más de X número de segundos), por lo tanto, communicatepuede tardar una eternidad en ejecutarse.

¿Cuál es la forma más sencilla de implementar tiempos de espera en un programa Python destinado a ejecutarse en Windows y Linux?

Sridhar Ratnakumar
fuente
2
Una entrada relacionada con el rastreador de problemas de Python: bugs.python.org/issue5673
Sridhar Ratnakumar
10
Use pypi.python.org/pypi/subprocess32 para Python2.x. Es un backport de Python 3.x. Tiene el argumento de tiempo de espera para call () y wait ().
guettli
1
pypi.python.org/pypi/subprocess32 no funciona en Windows :(
adrianX

Respuestas:

170

En Python 3.3+:

from subprocess import STDOUT, check_output

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

output es una cadena de bytes que contiene datos stdout, stderr combinados del comando.

check_outputaumenta CalledProcessErroren el estado de salida distinto de cero como se especifica en el texto de la pregunta a diferencia del proc.communicate()método.

He eliminado shell=Trueporque a menudo se usa innecesariamente. Siempre puede volver a agregarlo si cmdrealmente lo requiere. Si agrega shell=True, es decir, si el proceso hijo genera sus propios descendientes; check_output()puede regresar mucho más tarde de lo que indica el tiempo de espera, vea Falla del tiempo de espera del subproceso .

La función de tiempo de espera está disponible en Python 2.x a través del subprocess32backport del módulo de subproceso 3.2+.

jfs
fuente
17
De hecho, y el soporte de tiempo de espera de subproceso existe en el backport subprocess32 que mantengo para usar en Python 2. pypi.python.org/pypi/subprocess32
gps
8
@gps Sridhar solicitó una solución multiplataforma, mientras que su backport solo admite POSIX: cuando lo probé, MSVC se quejó (se esperaba) de la falta de unistd.h :)
Shmil The Cat
Si no necesita la salida, puede usar el subprocess.call.
Kyle Gibson
Desde Python3.5, use subprocess.run () con capture_output = True y use el parámetro de codificación para obtener la salida de usefoul.
MKesper
1
@MKesper: 1- check_output()es la forma preferida de obtener salida (devuelve la salida directamente, no ignora los errores, está disponible desde siempre). 2- run()es más flexible pero run()ignora el error por defecto y requiere pasos adicionales para obtener la salida 3- check_output()se implementa en términos derun() y, por lo tanto, acepta la mayoría de los mismos argumentos. 4- nit: capture_outputestá disponible desde 3.7, no 3.5
jfs
205

No sé mucho sobre los detalles de bajo nivel; pero, dado que en python 2.6 la API ofrece la capacidad de esperar subprocesos y finalizar procesos, ¿qué pasa con la ejecución del proceso en un subproceso separado?

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)

El resultado de este fragmento en mi máquina es:

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

donde se puede ver que, en la primera ejecución, el proceso finalizó correctamente (código de retorno 0), mientras que en la segunda el proceso finalizó (código de retorno -15).

No he probado en Windows; pero, aparte de actualizar el comando de ejemplo, creo que debería funcionar ya que no he encontrado en la documentación nada que diga que thread.join o process.terminate no sea compatible.

jcollado
fuente
16
+1 Por ser plataforma independiente. Lo ejecuté tanto en Linux como en Windows 7 (Cygwin y Windows sin formato Python): funciona como se esperaba en los tres casos.
phooji
77
Modifiqué un poco tu código para poder pasar kwargs nativos de Popen y ponerlo en esencia. Ahora está listo para usar multipropósito; gist.github.com/1306188
kirpit
2
Para cualquiera que tenga el problema que tenía @redice, esta pregunta puede ayudar. En resumen, si usa shell = True, el shell se convierte en el proceso hijo que se anula, y su comando (hijo del proceso hijo) sigue vivo.
Anson
66
Esta respuesta no proporciona la misma funcionalidad del original ya que no devuelve stdout.
stephenbez
2
thread.is_alive puede conducir a una condición de carrera. Ver ostricher.com/2015/01/python-subprocess-with-timeout
ChaimKut
132

La respuesta de jcollado se puede simplificar usando la clase 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
fuente
11
+1 para una solución portátil simple. No necesita lambda:t = Timer(timeout, proc.kill)
jfs
3
+1 Esta debería ser la respuesta aceptada, porque no requiere que se cambie la forma en que se inicia el proceso.
Dave Branton
1
¿Por qué requiere la lambda? ¿No podría usarse el método enlazado p.kill sin la lambda allí?
Danny Staple
//, ¿Estarías dispuesto a incluir un ejemplo del uso de esto?
Nathan Basanese
1
@tuk timer.isAlive()antes timer.cancel()significa que terminó normalmente
Charles
83

Si estás en 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
Alex Martelli
fuente
3
Bueno, estoy interesado en una solución multiplataforma que funcione al menos en win / linux / mac.
Sridhar Ratnakumar
1
Me gusta este enfoque basado en Unix. Idealmente, uno combinaría esto con un enfoque específico de Windows (usando CreateProcess y Jobs) ... pero por ahora, la solución a continuación es simple, fácil y funciona hasta ahora.
Sridhar Ratnakumar el
3
He agregado una solución portátil, mira mi respuesta
flybywire
44
Esta solución funcionaría solo si se llama a signal.signal (signal.SIGALARM, alarm_handler) desde el hilo principal. Consulte la documentación para la señal
volatilevoid
Desafortunadamente, cuando se ejecuta (en Linux) en el contexto de un módulo Apache (como mod_python, mod_perl o mod_php), descubrí que el uso de señales y alarmas no está permitido (presumiblemente porque interfieren con la lógica IPC de Apache). Entonces, para lograr el objetivo de agotar el tiempo de espera de un comando, me vi obligado a escribir "bucles primarios" que inician un proceso secundario y luego se sientan en un bucle "de suspensión" mirando el reloj (y posiblemente también monitoreando la salida del elemento secundario).
Peter
44

Aquí está la solución de Alex Martelli como un módulo con la eliminación adecuada del proceso. Los otros enfoques no funcionan porque no usan proc.communicate (). Entonces, si tiene un proceso que produce mucha salida, llenará su búfer de salida y luego se bloqueará hasta que lea algo de él.

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
fuente
3
Esto no funcionará en Windows, además se invierte el orden de las funciones.
Hamish Grubijan el
3
Esto a veces resulta en una excepción cuando otro controlador se registra en SIGALARM y mata el proceso antes de que este "mate", agregó una solución alternativa. Por cierto, gran receta! Lo he usado para lanzar 50,000 procesos con errores hasta el momento sin congelar o bloquear el contenedor de manejo.
Yaroslav Bulatov
¿Cómo se puede modificar esto para que se ejecute en una aplicación Threaded? Estoy tratando de utilizarlo dentro de subprocesos de trabajo y obtenerValueError: signal only works in main thread
Wim
@Yaroslav Bulatov Gracias por la información. ¿Cuál fue la solución que agregó para tratar el problema mencionado?
jpswain
1
Acabo de agregar el bloque "try; catch", está dentro del código. Por cierto, a largo plazo, esto me dio problemas porque solo puede configurar un controlador SIGALARM, y otros procesos pueden restablecerlo. Aquí se da una solución a esto: stackoverflow.com/questions/6553423/…
Yaroslav Bulatov
18

He modificado la respuesta de sussudio . Ahora función devuelve: ( returncode, stdout, stderr, timeout) - stdouty stderrse decodifica a UTF-8 cuerdas

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"]
Michal Zmuda
fuente
18

sorprendido nadie mencionó usar timeout

timeout 5 ping -c 3 somehost

Obviamente, esto no funcionará para todos los casos de uso, pero si se trata de un script simple, es difícil de superar.

También disponible como gtimeout en coreutils a través homebrewde usuarios de mac.

Karsten
fuente
1
Se refiere a: proc = subprocess.Popen(['/usr/bin/timeout', str(timeout)] + cmd, ...). ¿Hay un timeoutcomando en Windows como OP pregunta?
jfs
En Windows, uno puede usar aplicaciones como git bash que permite utilidades bash en Windows.
Kaushik Acharya
@KaushikAcharya, incluso si usa git bash, cuando Python llama al subproceso, se ejecutará en Windows, por lo tanto, esta omisión no funcionará.
Naman Chikara
16

timeoutahora es compatible con call()y communicate()en el módulo de subproceso (a partir de Python3.3):

import subprocess

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

Esto llamará al comando y generará la excepción

subprocess.TimeoutExpired

si el comando no termina después de 20 segundos.

Luego puede manejar la excepción para continuar su código, algo como:

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

Espero que esto ayude.

James
fuente
Hay una respuesta existente que menciona el timeoutparámetro . Aunque mencionarlo una vez más no estaría de más.
jfs
//, creo que OP está buscando una solución para el Python anterior.
Nathan Basanese
11

Otra opción es escribir en un archivo temporal para evitar el bloqueo stdout en lugar de tener que sondear con comunicar (). Esto funcionó para mí donde las otras respuestas no lo hicieron; por ejemplo en 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()
Mate
fuente
Parece incompleto: ¿qué es el archivo temporal?
spiderplant0
¡Incluya "import tempfile", "import time" y "shell = True" dentro de la llamada "Popen" (tenga cuidado con "shell = True")!
Eduardo Lucio
11

No sé por qué no se menciona, pero desde Python 3.5, hay un nuevo subprocess.runcomando universal (que está destinado a reemplazar check_call, check_output...) y que tiene eltimeout parámetro.

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

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

Provoca una subprocess.TimeoutExpiredexcepción cuando el tiempo de espera ha expirado.

Jean-François Fabre
fuente
6

Aquí está mi solución, estaba usando Thread and 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

En acción:

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

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

La solución que uso es prefijar el comando de shell con timelimit . Si el comando tarda demasiado, timelimit lo detendrá y Popen tendrá un código de retorno establecido por timelimit. Si es> 128, significa que el límite de tiempo mató el proceso.

Consulte también el subproceso de python con tiempo de espera y salida grande (> 64K)

revs bortzmeyer
fuente
Uso una herramienta similar llamada timeout- packages.ubuntu.com/search?keywords=timeout - pero ninguna de las dos funciona en Windows, ¿verdad?
Sridhar Ratnakumar
5

Agregué la solución con subprocesos jcolladoa mi módulo Python easyprocess .

Instalar en pc:

pip install easyprocess

Ejemplo:

from easyprocess import Proc

# shell is not supported!
stdout=Proc('ping localhost').call(timeout=1.5).stdout
print stdout
ponty
fuente
El módulo easyprocess ( code.activestate.com/pypm/easyprocess ) funcionó para mí, incluso usándolo desde multiprocesamiento.
iChux
5

si estás usando python 2, pruébalo

import subprocess32

try:
    output = subprocess32.check_output(command, shell=True, timeout=3)
except subprocess32.TimeoutExpired as e:
    print e
ThOong Ku
fuente
1
Probablemente no funciona en Windows, como se hizo en la pregunta inicial
Jean-Francois T.
5

Anteponer el comando de Linux timeoutno es una mala solución y funcionó para mí.

cmd = "timeout 20 "+ cmd
subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
Vikram Hosakote
fuente
¿Cómo puedo imprimir las cadenas de salida durante la ejecución del subproceso? - Los mensajes de salida se devuelven por subproceso.
Ammad
3

He implementado lo que pude reunir de algunos de estos. Esto funciona en Windows, y como se trata de una wiki comunitaria, creo que también compartiría mi código:

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

Luego de otra clase o archivo:

        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
fuente
En realidad, esto probablemente no funciona. ¡La terminate()función marca un hilo como terminado, pero en realidad no termina el hilo! Puedo verificar esto en * nix, pero no tengo una computadora con Windows para probar.
dotancohen
2

Una vez que comprenda el proceso completo que ejecuta la maquinaria en * unix, encontrará fácilmente una solución más simple:

Considere este ejemplo simple de cómo hacer que el método de comunicación () se pueda cronometrar usando select.select () (disponible casi en todas partes en * nix hoy en día). Esto también se puede escribir con epoll / poll / kqueue, pero la variante select.select () podría ser un buen ejemplo para usted. Y las principales limitaciones de select.select () (velocidad y 1024 fds máximos) no son aplicables para su tarea.

Esto funciona bajo * nix, no crea subprocesos, no usa señales, se puede iniciar desde cualquier subproceso (no solo principal) y lo suficientemente rápido como para leer 250 mb / s de datos desde stdout en mi máquina (i5 2.3ghz).

Hay un problema al unir stdout / stderr al final de la comunicación. Si tiene una gran salida de programa, esto podría llevar a un gran uso de memoria. Pero puede llamar a comunicarse () varias veces con tiempos de espera más pequeños.

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
fuente
2
Esto solo soluciona la mitad del problema de Unix.
Spaceghost
2

Puedes hacer esto 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
dspeyer
fuente
1

He usado killableprocess con éxito en Windows, Linux y Mac. Si está utilizando Cygwin Python, necesitará la versión de OSAF de killableprocess porque de lo contrario los procesos nativos de Windows no serán eliminados.

Heikki Toivonen
fuente
Parece que killableprocess no agrega un tiempo de espera a la llamada Popen.communicate ().
Wim Coenen
1

Aunque no lo he mirado extensamente, este decorador que encontré en ActiveState parece ser bastante útil para este tipo de cosas. Junto con subprocess.Popen(..., close_fds=True), al menos, estoy listo para el script de shell en Python.

Ehtesh Choudhury
fuente
Este decorador usa signal.alarm, que no está disponible en Windows.
dbn
1

Esta solución mata el árbol de procesos en caso de shell = True, pasa parámetros al proceso (o no), tiene un tiempo de espera y obtiene la salida stdout, stderr y proceso de la llamada (usa psutil para kill_proc_tree). Esto se basó en varias soluciones publicadas en SO, incluidas las de jcollado. Publicación en respuesta a los comentarios de Anson y jradice en la respuesta de jcollado. Probado en Windows Srvr 2012 y Ubuntu 14.04. Tenga en cuenta que para Ubuntu necesita cambiar la llamada parent.children (...) a 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()
Tomás
fuente
1

Existe la idea de subclasificar la clase Popen y extenderla con algunos decoradores de métodos simples. Llamémoslo 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 [email protected]', stdout=PIPE, timeout=1).communicate()
Alexander Yakovlev
fuente
1

Tuve el problema de que quería terminar un subproceso de subprocesos múltiples si tardaba más de un tiempo de espera determinado. Quería establecer un tiempo de espera Popen(), pero no funcionó. Entonces, me di cuenta de que Popen().wait()es igual call()y tuve la idea de establecer un tiempo de espera dentro del .wait(timeout=xxx)método, que finalmente funcionó. Por lo tanto, lo resolví de esta manera:

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()
mayor
fuente
0

Desafortunadamente, estoy sujeto a políticas muy estrictas sobre la divulgación del código fuente por parte de mi empleador, por lo que no puedo proporcionar el código real. Pero para mi gusto, la mejor solución es crear una subclase que anule la Popen.wait()encuesta en lugar de esperar indefinidamente y Popen.__init__aceptar un parámetro de tiempo de espera. Una vez que haga eso, todos los demás Popenmétodos (que llaman wait) funcionarán como se espera, incluidos communicate.

Patrick Narkinsky
fuente
0

https://pypi.python.org/pypi/python-subprocess2 proporciona extensiones al módulo de subproceso que le permiten esperar hasta un cierto período de tiempo, de lo contrario, finalizar.

Por lo tanto, esperar hasta 10 segundos para que el proceso finalice, de lo contrario, elimine:

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

timeout =  10

results = pipe.waitOrTerminate(timeout)

Esto es compatible con Windows y Unix. "results" es un diccionario, contiene "returnCode", que es el retorno de la aplicación (o None si se tuvo que eliminar), así como "actionTaken". que será "SUBPROCESS2_PROCESS_COMPLETED" si el proceso se completa normalmente, o una máscara de "SUBPROCESS2_PROCESS_TERMINATED" y SUBPROCESS2_PROCESS_KILLED dependiendo de la acción realizada (consulte la documentación para obtener más detalles)

Tim Savannah
fuente
0

para python 2.6+, use 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
fuente
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
fuente
-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
fuente
esto es una abominación
Corey Goldberg
-2

Solo estaba tratando de escribir algo más simple.

#!/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
fuente
time.sleep (1) es una muy mala idea: imagine que desea ejecutar muchos comandos que tomarían aproximadamente 0.002seg. Debería esperar mientras poll () (vea select, para Linux epol recomendado :)
ddzialak