Запустите процесс и убейте его, если он не завершится в течение часа

Мне нужно сделать следующее в Python. Я хочу создать процесс (модуль подпроцесса?) И:

  • если процесс завершается нормально, чтобы продолжить с момента его завершения;
  • если, в противном случае, процесс "застревает" и не заканчивается в течение (скажем) часа, чтобы убить его и продолжить (возможно, давая ему еще одну попытку в цикле).

Каков самый элегантный способ сделать это?

Ответы

Ответ 1

Модуль subprocess будет вашим другом. Запустите процесс, чтобы получить объект Popen, затем передайте его функции, подобной этой. Обратите внимание, что это исключает только тайм-аут. При желании вы можете поймать исключение и вызвать метод kill() в процессе Popen. (kill является новым в Python 2.6, кстати)

import time

def wait_timeout(proc, seconds):
    """Wait for a process to finish, or raise exception after timeout"""
    start = time.time()
    end = start + seconds
    interval = min(seconds / 1000.0, .25)

    while True:
        result = proc.poll()
        if result is not None:
            return result
        if time.time() >= end:
            raise RuntimeError("Process timed out")
        time.sleep(interval)

Ответ 2

Есть как минимум 2 способа сделать это с помощью psutil, если вы знаете PID процесса. Предполагая, что процесс создан так:

import subprocess
subp = subprocess.Popen(['progname'])

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

import psutil, time

TIMEOUT = 60 * 60  # 1 hour

p = psutil.Process(subp.pid)
while 1:
    if (time.time() - p.create_time()) > TIMEOUT:
        p.kill()
        raise RuntimeError('timeout')
    time.sleep(5)

... или просто, вы можете сделать это:

import psutil

p = psutil.Process(subp.pid)
try
    p.wait(timeout=60*60)
except psutil.TimeoutExpired:
    p.kill()
    raise

Кроме того, пока вы занимаетесь этим, вас могут заинтересовать следующие дополнительные API:

>>> p.status()
'running'
>>> p.is_running()
True
>>>

Ответ 3

У меня был аналогичный вопрос и нашла этот ответ. Просто для полноты, я хочу добавить еще один способ, как прекратить висячий процесс через заданный промежуток времени: библиотека сигналов питона https://docs.python.org/2/library/signal.html

Из документации:

import signal, os

def handler(signum, frame):
    print 'Signal handler called with signal', signum
    raise IOError("Couldn't open device!")

# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

# This open() may hang indefinitely
fd = os.open('/dev/ttyS0', os.O_RDWR)

signal.alarm(0)          # Disable the alarm

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

Ответ 4

Хороший, пассивный способ - также использовать threading.Timer и настроить функцию обратного вызова.

from threading import Timer

# execute the command
p = subprocess.Popen(command)

# save the proc object - either if you make this onto class (like the example), or 'p' can be global
self.p == p

# config and init timer
# kill_proc is a callback function which can also be added onto class or simply a global
t = Timer(seconds, self.kill_proc)

# start timer
t.start()

# wait for the test process to return
rcode = p.wait()

t.cancel()

Если процесс завершается во времени, wait() заканчивается и код продолжается здесь, cancel() останавливает таймер. Если между тем таймер заканчивается и выполняет kill_proc в отдельном потоке, wait() также продолжит работу здесь и cancel() ничего не сделает. По значению rcode вы будете знать, если мы тайм-аут или нет. Простой kill_proc: (вы можете, конечно, сделать что-нибудь дополнительно)

def kill_proc(self):
    os.kill(self.p, signal.SIGTERM)

Ответ 5

Приветствую Питера Шиннерса за его замечательное предложение по модулю subprocess. Я использовал exec() раньше и не имел никакого контроля над временем выполнения и особенно прерывал его. Мой простейший шаблон для такого рода задач следующий: я просто использую параметр тайм-аута функции subprocess.run() для контроля времени работы. Конечно, вы можете получить стандартную ошибку и ошибку, если это необходимо:

from subprocess import run, TimeoutExpired, CalledProcessError

for file in fls:
    try:
        run(["python3.7", file], check=True, timeout=7200)  # 2 hours timeout
        print("scraped :)", file)
    except TimeoutExpired:
        message = "Timeout :( !!!"
        print(message, file)
        f.write("{message} {file}\n".format(file=file, message=message))
    except CalledProcessError:
        message = "SOMETHING HAPPENED :( !!!, CHECK"
        print(message, file)
        f.write("{message} {file}\n".format(file=file, message=message))