Запустите процесс и убейте его, если он не завершится в течение часа
Мне нужно сделать следующее в 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))