web-dev-qa-db-ja.com

タイムアウト時にサブプロセスを強制終了または終了しますか?

サブプロセスをできるだけ速く繰り返し実行したいと思います。ただし、時間がかかりすぎることがあるので、やめたいと思います。私は以下のようにsignal.signal(...)を使用します:

ppid=pipeexe.pid
signal.signal(signal.SIGALRM, stop_handler)

signal.alarm(1)
.....
def stop_handler(signal, frame):
    print 'Stop test'+testdir+'for time out'
    if(pipeexe.poll()==None and hasattr(signal, "SIGKILL")):
         os.kill(ppid, signal.SIGKILL)
         return False

しかし、いつかこのコードは次のラウンドの実行を停止しようとします。 test/home/lu/workspace/152/treefit/test2forタイムアウトを停止します/ bin/sh:/ home/lu/workspace/153/squib_driver:見つかりません---これは次の実行です。プログラムが誤って停止します。

誰かがこれを解決する方法を知っていますか? 1秒実行せずに時間内に停止したいtime.sleep(n)はn秒待つことが多い。 1秒未満で実行できるようにしたい

17
user504909

あなたはこのようなことをすることができます:

import subprocess as sub
import threading

class RunCmd(threading.Thread):
    def __init__(self, cmd, timeout):
        threading.Thread.__init__(self)
        self.cmd = cmd
        self.timeout = timeout

    def run(self):
        self.p = sub.Popen(self.cmd)
        self.p.wait()

    def Run(self):
        self.start()
        self.join(self.timeout)

        if self.is_alive():
            self.p.terminate()      #use self.p.kill() if process needs a kill -9
            self.join()

RunCmd(["./someProg", "arg1"], 60).Run()

コマンドを実行するスレッドを作成し、タイムアウトが適切な値(この場合は60秒)を超えた場合にスレッドを強制終了するという考え方です。

41
ralphtheninja

これは、サブプロセス実行のウォッチドッグとして私が書いたものです。今はよく使っていますが、あまり経験がないので、いくつかの欠陥があるかもしれません。

_import subprocess
import time

def subprocess_execute(command, time_out=60):
    """executing the command with a watchdog"""

    # launching the command
    c = subprocess.Popen(command)

    # now waiting for the command to complete
    t = 0
    while t < time_out and c.poll() is None:
        time.sleep(1)  # (comment 1)
        t += 1

    # there are two possibilities for the while to have stopped:
    if c.poll() is None:
        # in the case the process did not complete, we kill it
        c.terminate()
        # and fill the return code with some error value
        returncode = -1  # (comment 2)

    else:                 
        # in the case the process completed normally
        returncode = c.poll()

    return returncode   
_

使用法:

_ return = subprocess_execute(['Java', '-jar', 'some.jar'])
_

コメント:

  1. ここでは、ウォッチドッグタイムアウトは秒単位です。ただし、time.sleep()値を変更することで、必要なものに簡単に変更できます。 _time_out_はそれに応じて文書化する必要があります。
  2. 必要なものに応じて、ここではいくつかの例外を発生させる方が適切かもしれません。

ドキュメント:_subprocess.Popen_がブロックされていないことを理解するために、subprocessモジュールのドキュメントに少し苦労しました。プロセスは並行して実行されます(ここでは正しい単語を使用していないかもしれませんが、理解できると思います)。

しかし、私が書いたものは実行が直線的であるため、コマンドのバグを回避してスクリプトの夜間の実行を一時停止するために、コマンドが完了するのを本当に待つ必要があります。

2
Joël

これは、スレッドとプロセスを使用したイベント指向プログラミングでよくある同期の問題だと思います。

常に1つのサブプロセスのみを実行する必要がある場合は、次のサブプロセスを実行する前に、現在のサブプロセスが強制終了されていることを確認してください。そうしないと、シグナルハンドラーが最後に実行されたサブプロセスへの参照を取得し、古いサブプロセスを無視する可能性があります。

サブプロセスAが実行されているとします。アラーム信号が処理される前に、サブプロセスBが起動されます。その直後に、アラーム信号ハンドラーがサブプロセスを強制終了しようとします。サブプロセスの起動時に現在のPID(または現在のサブプロセスパイプオブジェクト)がBに設定されているため、Bは強制終了され、Aは実行を継続します。

私の推測は正しいですか?

コードを理解しやすくするために、現在のサブプロセスを強制終了する部分の直後に、新しいサブプロセスを作成する部分を含めます。これにより、常に実行されているサブプロセスは1つだけであることが明らかになります。シグナルハンドラーは、ループで実行される反復ブロックであるかのように、サブプロセスの強制終了と起動の両方を実行できます。この場合、1秒ごとにアラーム信号でイベント駆動型になります。

0
scoffey

これが私が使用するものです:

class KillerThread(threading.Thread):
  def __init__(self, pid, timeout, event ):
    threading.Thread.__init__(self)
    self.pid = pid
    self.timeout = timeout
    self.event = event
    self.setDaemon(True)
  def run(self):
    self.event.wait(self.timeout)
    if not self.event.isSet() :
      try:
        os.kill( self.pid, signal.SIGKILL )
      except OSError, e:
        #This is raised if the process has already completed
        pass    

def runTimed(dt, dir, args, kwargs ):
  event = threading.Event()
  cwd = os.getcwd()
  os.chdir(dir)
  proc = subprocess.Popen(args, **kwargs )
  os.chdir(cwd)
  killer = KillerThread(proc.pid, dt, event)
  killer.start()

  (stdout, stderr) = proc.communicate()
  event.set()      

  return (stdout,stderr, proc.returncode)
0

もう少し複雑なことに、 同様の問題を解決するための回答 :stdoutをキャプチャし、stdinにフィードし、しばらく非アクティブにした後、および/または全体的な実行後に終了できるようにしました。

0
cfi