web-dev-qa-db-ja.com

プロセスを実行し、1時間以内に終了しない場合は強制終了します

Pythonで次のことを行う必要があります。プロセス(サブプロセスモジュール?)を生成したいのですが、次のようになります。

  • プロセスが正常に終了した場合、終了した瞬間から正確に続行します。
  • それ以外の場合、プロセスが「スタック」し、(たとえば)1時間以内に終了しない場合は、プロセスを強制終了して続行します(ループ内でもう一度試行する可能性があります)。

これを達成するための最もエレガントな方法は何ですか?

21

subprocessモジュールがあなたの友達になります。プロセスを開始してPopenオブジェクトを取得し、それを次のような関数に渡します。これはタイムアウト時にのみ例外を発生させることに注意してください。必要に応じて、例外をキャッチし、Popenプロセスでkill()メソッドを呼び出すことができます。 (killはPython 2.6、btw)の新機能です)

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)
28
Peter Shinners

プロセスPIDを知っている限り、 psutil を使用してこれを行うには少なくとも2つの方法があります。プロセスがそのように作成されていると仮定します。

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
>>>
8

私は同様の質問をし、この答えを見つけました。完全を期すために、指定された時間が経過した後にハングしているプロセスを終了する方法をもう1つ追加したいと思います。pythonシグナルライブラリ 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

とにかく新しいプロセスを生成したかったので、これは問題の最善の解決策ではないかもしれません。

3
asPlankBridge

優れたパッシブな方法は、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)
1
vhumpa

subprocessモジュールについての彼の素晴らしい提案に対してKoodosからPeterShinnersへ。以前は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))

0
Edgar Manukyan