Python, Popen и select - ожидание завершения процесса или тайм-аут - PullRequest
22 голосов
/ 03 декабря 2008

Я запускаю подпроцесс, используя:

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

Этот подпроцесс может либо сразу завершиться с ошибкой на stderr, либо продолжить работу. Я хочу обнаружить любое из этих условий - последнее, подождав несколько секунд.

Я пробовал это:

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

но он просто возвращает:

  ([],[],[])

при любом условии. Что я могу сделать?

Ответы [ 7 ]

15 голосов
/ 03 декабря 2008

Вы пытались использовать метод Popen.Poll (). Вы могли бы просто сделать это:

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

Это заставит вас всегда ждать 10 секунд, но если случай неудачи будет редким, он будет амортизироваться во всех случаях успеха.


Edit:

Как насчет:

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

Уродливое ожидание занято, но я думаю, что оно выполняет то, что вы хотите.

Дополнительно, снова просматривая документацию по выбору вызовов, я думаю, что вы можете изменить ее следующим образом:

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

Поскольку вы обычно хотите читать из stderr, вы хотите знать, когда у него есть что-то доступное для чтения (т. Е. Случай сбоя).

Надеюсь, это поможет.

8 голосов
/ 24 июня 2009

Это то, что я придумал. Работает, когда вам нужно, и вам не нужно время ожидания для процесса pp, но с полу-занятым циклом.

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)
2 голосов
/ 11 мая 2011

Вот хороший пример:

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 голосов
/ 03 декабря 2008

Использование выберите и сон не имеет особого смысла. select (или любой механизм опроса ядра) по своей сути полезен для асинхронного программирования, но ваш пример является синхронным. Поэтому либо переписайте свой код, чтобы использовать обычный способ блокировки, либо рассмотрите возможность использования 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()

Между прочим, есть более безопасный способ сделать это с помощью Twisted, написав свой собственный ProcessProtocol:

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

1 голос
/ 12 ноября 2012

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

См. Документы с истекшим сроком действия .

1 голос
/ 04 декабря 2008

Если, как вы сказали в комментариях выше, вы просто настраиваете вывод каждый раз и повторно запускаете команду, будет ли что-то вроде следующей работы?

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

Приведенный выше код выводит:

python did not quit within the given time period.

Единственный недостаток приведенного выше кода, о котором я могу подумать, это потенциально перекрывающиеся процессы, когда вы продолжаете выполнять check_cmd.

0 голосов
/ 11 марта 2013

Это парафраз ответа Эвана, но он учитывает следующее:

  1. Явная отмена объекта Timer: если интервал Timer будет длинным и процесс завершится по собственной воле, это может повредить ваш скрипт: (
  2. В подходе Timer есть внутренняя гонка (попытка таймера убить процесс сразу после процесса, и это в Windows вызовет исключение).

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