web-dev-qa-db-fra.com

Python, Popen et sélectionnez - en attente de la fin d'un processus ou d'un délai d'attente

Je lance un sous-processus en utilisant:

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

Ce sous-processus peut soit se terminer immédiatement avec une erreur sur stderr, soit continuer à fonctionner. Je veux détecter l'une ou l'autre de ces conditions - cette dernière en attendant plusieurs secondes.

J'ai essayé ceci:

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

mais ça revient juste:

  ([],[],[])

à chaque condition. Que puis-je faire?

22
George Yuan

Avez-vous essayé d'utiliser la méthode Popen.Poll (). Vous pouvez simplement faire ceci:

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

Cela vous obligera à toujours attendre 10 secondes, mais si le cas d'échec est rare, il sera amorti sur tous les cas de réussite.


Modifier:

Que diriez-vous:

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

Cela a la laideur d'être une attente occupée, mais je pense que cela accomplit ce que vous voulez.

De plus, en regardant de nouveau la documentation sur les appels sélectifs, je pense que vous voudrez peut-être la modifier comme suit:

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

Puisque vous voudriez généralement lire à partir de stderr, vous voulez savoir quand il y a quelque chose à lire (par exemple, le cas d'échec).

J'espère que ça aide.

15
grieve

C'est ce que je suis venu avec. Fonctionne lorsque vous avez besoin et que vous n'avez pas besoin de délai d'expiration sur le processus thep, mais avec une boucle semi-occupée.

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)
8
Darjus Loktevic

Voici un bel exemple:

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

Utiliser select et dormir n'a pas vraiment de sens. select (ou tout mécanisme de scrutation du noyau) est intrinsèquement utile pour la programmation asynchrone, mais votre exemple est synchrone. Donc, réécrivez votre code pour utiliser le mode de blocage normal ou envisagez d’utiliser 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()

À propos, il existe un moyen plus sûr de le faire avec Twisted en écrivant votre propre ProcessProtocol:

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

1

Si, comme vous l'avez dit dans les commentaires ci-dessus, vous modifiez simplement la sortie à chaque fois et réexécutez la commande, le travail suivant pourrait-il fonctionner?

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

Le code ci-dessus, lorsqu'il est exécuté, affiche:

python did not quit within the given time period.

Le seul inconvénient du code ci-dessus auquel je puisse penser est le risque de chevauchement des processus au fur et à mesure de l'exécution de check_cmd.

1
shsmurfy

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

Voir Documents expirés Timeout .

1
jfs

Ceci est une paraphrase de la réponse d'Evan, mais il prend en compte les éléments suivants:

  1. Annulation explicite de l’objet Minuteur: si l’intervalle du minuteur est long et que le processus se termine de son propre volonté, cela pourrait bloquer votre script :(
  2. Il y a une course intrinsèque dans l'approche du minuteur (la tentative du minuteur tuant le processus juste après le processus est mort et ceci sur Windows lève une exception). 

      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()
    
0
Shmil The Cat