Python, Popen y seleccione: esperando a que finalice un proceso o se agote el tiempo de espera

Ejecuto un subproceso utilizando:

p = subprocess.Popen("subprocess", stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 

Este subproceso podría salir inmediatamente con un error en stderr, o seguir funcionando. Quiero detectar cualquiera de estas condiciones, la última esperando varios segundos.

Intenté esto:

  SECONDS_TO_WAIT = 10 select.select([], [p.stdout, p.stderr], [p.stdout, p.stderr], SECONDS_TO_WAIT) 

pero simplemente vuelve:

  ([],[],[]) 

en cualquier condición ¿Que puedo hacer?

¿Has intentado usar el método Popen.Poll ()? Usted podría hacer esto:

 p = subprocess.Popen("subprocess", stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) time.sleep(SECONDS_TO_WAIT) retcode = p.poll() if retcode is not None: # process has terminated 

Esto hará que siempre espere 10 segundos, pero si el caso de falla es raro, se amortizará en todos los casos de éxito.


Editar:

Qué tal si:

 t_nought = time.time() seconds_passed = 0 while(p.poll() is not None and seconds_passed < 10): seconds_passed = time.time() - t_nought if seconds_passed >= 10: #TIMED OUT 

Esto tiene la fealdad de ser una espera ocupada, pero creo que logra lo que quieres.

Además, mirando de nuevo la documentación de selección de llamada, creo que es posible que desee cambiarla de la siguiente manera:

 SECONDS_TO_WAIT = 10 select.select([p.stderr], [], [p.stdout, p.stderr], SECONDS_TO_WAIT) 

Dado que normalmente deseará leer desde stderr, querrá saber cuándo tiene algo disponible para leer (es decir, el caso de falla).

Espero que esto ayude.

Esto es lo que se me ocurrió. Funciona cuando se necesita y no es necesario que se agote el tiempo de espera en el proceso de pp, pero sí con un bucle de ocupado.

 def runCmd(cmd, timeout=None): ''' Will execute a command, read the output and return it back. @param cmd: command to execute @param timeout: process timeout in seconds @return: a tuple of three: first stdout, then stderr, then exit code @raise OSError: on missing command or if a timeout was reached ''' ph_out = None # process output ph_err = None # stderr ph_ret = None # return code p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # if timeout is not set wait for process to complete if not timeout: ph_ret = p.wait() else: fin_time = time.time() + timeout while p.poll() == None and fin_time > time.time(): time.sleep(1) # if timeout reached, raise an exception if fin_time < time.time(): # starting 2.6 subprocess has a kill() method which is preferable # p.kill() os.kill(p.pid, signal.SIGKILL) raise OSError("Process timeout has been reached") ph_ret = p.returncode ph_out, ph_err = p.communicate() return (ph_out, ph_err, ph_ret) 

Usar seleccionar y dormir realmente no tiene mucho sentido. La selección (o cualquier mecanismo de sondeo del kernel) es inherentemente útil para la progtwigción asíncrona, pero su ejemplo es síncrono. Entonces, reescriba su código para usar el modo de locking normal o considere usar Twisted:

 from twisted.internet.utils import getProcessOutputAndValue from twisted.internet import reactor def stop(r): reactor.stop() def eb(reason): reason.printTraceback() def cb(result): stdout, stderr, exitcode = result # do something getProcessOutputAndValue('/bin/someproc', [] ).addCallback(cb).addErrback(eb).addBoth(stop) reactor.run() 

Por cierto, hay una forma más segura de hacerlo con Twisted al escribir su propio ProcessProtocol:

http://twistedmatrix.com/projects/core/documentation/howto/process.html

Aquí hay un buen ejemplo:

 from threading import Timer from subprocess import Popen, PIPE def kill_proc(): proc.kill() proc = Popen("ping 127.0.0.1", shell=True) t = Timer(60, kill_proc) t.start() proc.wait() 

Si, como dijiste en los comentarios anteriores, solo estás modificando la salida cada vez y volviendo a ejecutar el comando, ¿funcionaría algo como lo siguiente?

 from threading import Timer import subprocess WAIT_TIME = 10.0 def check_cmd(cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def _check(): if p.poll()!=0: print cmd+" did not quit within the given time period." # check whether the given process has exited WAIT_TIME # seconds from now Timer(WAIT_TIME, _check).start() check_cmd('echo') check_cmd('python') 

El código anterior, cuando se ejecuta, genera:

 python did not quit within the given time period. 

El único inconveniente del código anterior en el que puedo pensar es en los procesos potencialmente superpuestos a medida que sigues ejecutando check_cmd.

Python 3.3

 import subprocess as sp try: sp.check_call(["/subprocess"], timeout=10, stdin=sp.DEVNULL, stdout=sp.DEVNULL, stderr=sp.DEVNULL) except sp.TimeoutError: # timeout (the subprocess is killed at this point) except sp.CalledProcessError: # subprocess failed before timeout else: # subprocess ended successfully before timeout 

Ver TimeoutExpired docs .

Esta es una paráfrasis de la respuesta de Evan, pero tiene en cuenta lo siguiente:

  1. Cancelación explícita del objeto del temporizador: si el intervalo del temporizador sería largo y el proceso saldrá por su “propia voluntad”, esto podría bloquear su script 🙁
  2. Hay una carrera intrínseca en el enfoque del temporizador (el bash del temporizador que mata el proceso justo después de que el proceso haya muerto y esto en Windows generará una excepción).

      DEVNULL = open(os.devnull, "wb") process = Popen("c:/myExe.exe", stdout=DEVNULL) # no need for stdout def kill_process(): """ Kill process helper""" try: process.kill() except OSError: pass # Swallow the error timer = Timer(timeout_in_sec, kill_process) timer.start() process.wait() timer.cancel()