Подтвердить что ты не робот

Python, Popen и select - ожидание завершения процесса или тайм-аута

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

  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)

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

  ([],[],[])

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

4b9b3361

Ответ 1

Вы пытались использовать метод 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, вы хотите знать, когда у него есть что-то доступное для чтения (т.е. случай сбоя).

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

Ответ 2

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

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)

Ответ 3

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

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

Ответ 4

Использование select и sleep действительно не имеет большого смысла. 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

Ответ 5

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

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.

Ответ 6

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

Смотрите TimeoutExpired docs.

Ответ 7

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

  • Явная отмена объекта Timer: если интервал таймера будет длинным, и процесс завершится по его "собственной воле", это может повесить ваш script: (
  • В подходе 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()