Несколько подпроцессов с таймаутами - PullRequest
7 голосов
/ 02 июля 2011

Я использую рецепт, который использует SIGALRM для установки прерывания тревоги - Использование модуля 'подпроцесс' с таймаутом

Проблема в том, что у меня есть несколько сценариев Python, использующих процесс signal.ALARM для установки тайм-аутов, и вызывается только последний сигнал тревоги. Что является хорошим способом улучшить время ожидания установки нескольких функций Python?

1 Ответ

11 голосов
/ 02 июля 2011

За исключением простых быстрых хаков, избегайте SIGALRM. Это очень старый, ограниченный механизм, не подходящий для чего-то более сложного: вы можете установить только один сигнал тревоги, и он прерывает любой системный вызов за раз, а не только тот, который вы намереваетесь прервать.

Гораздо чище использовать поток тайм-аута для остановки процесса, например:

import subprocess, signal, os, threading, errno
from contextlib import contextmanager

class TimeoutThread(object):
    def __init__(self, seconds):
        self.seconds = seconds
        self.cond = threading.Condition()
        self.cancelled = False
        self.thread = threading.Thread(target=self._wait)

    def run(self):
        """Begin the timeout."""
        self.thread.start()

    def _wait(self):
        with self.cond:
            self.cond.wait(self.seconds)

            if not self.cancelled:
                self.timed_out()

    def cancel(self):
        """Cancel the timeout, if it hasn't yet occured."""
        with self.cond:
            self.cancelled = True
            self.cond.notify()
        self.thread.join()

    def timed_out(self):
        """The timeout has expired."""
        raise NotImplementedError

class KillProcessThread(TimeoutThread):
    def __init__(self, seconds, pid):
        super(KillProcessThread, self).__init__(seconds)
        self.pid = pid

    def timed_out(self):
        try:
            os.kill(self.pid, signal.SIGKILL)
        except OSError as e:
            # If the process is already gone, ignore the error.
            if e.errno not in (errno.EPERM, errno. ESRCH):
                raise e

@contextmanager
def processTimeout(seconds, pid):
    timeout = KillProcessThread(seconds, pid)
    timeout.run()
    try:
        yield
    finally:
        timeout.cancel()


def example():
    proc = subprocess.Popen(["sleep", "5"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

    with processTimeout(1, proc.pid):
        print proc.communicate()

    resultcode = proc.wait()
    if resultcode < 0:
        print "error: %i" % resultcode

if __name__ == '__main__':
    example()

В зависимости от того, на какое время вы рассчитываете, вы можете использовать более слабый сигнал, чем SIGKILL, чтобы процесс очистки мог очищаться после себя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...