web-dev-qa-db-ja.com

Pythonサブプロセスタイムアウト?

Pythonのsubprocess.Popenメソッドのタイムアウトを設定するための引数やオプションはありますか?

このようなもの:

subprocess.Popen(['..'], ..., timeout=20)

18
sultan

スレッドモジュールの タイマークラス を確認することをお勧めします。 Popenのタイムアウトを実装するために使用しました。

まず、コールバックを作成します。

    def timeout( p ):
        if p.poll() is None:
            print 'Error: process taking too long to complete--terminating'
            p.kill()

次に、プロセスを開きます。

    proc = Popen( ... )

次に、プロセスを渡すコールバックを呼び出すタイマーを作成します。

    t = threading.Timer( 10.0, timeout, [proc] )
    t.start()
    t.join()

プログラムの後半のどこかに、次の行を追加することをお勧めします。

    t.cancel()

それ以外の場合、pythonプログラムは、タイマーの実行が終了するまで実行を続けます。

編集:サブプロセスpがp.poll()呼び出しとp.kill()呼び出しの間に終了する可能性があるという競合状態があるとアドバイスされました。私は次のコードがそれを修正できると信じています:

    import errno

    def timeout( p ):
        if p.poll() is None:
            try:
                p.kill()
                print 'Error: process taking too long to complete--terminating'
            except OSError as e:
                if e.errno != errno.ESRCH:
                    raise

ただし、例外処理をクリーンアップして、サブプロセスがすでに正常に終了したときに発生する特定の例外のみを具体的に処理することもできます。

15
dvntehn00bz

subprocess.Popenはブロックしないので、次のようなことができます。

import time

p = subprocess.Popen(['...'])
time.sleep(20)
if p.poll() is None:
  p.kill()
  print 'timed out'
else:
  print p.communicate()

終了するまで常に少なくとも20秒待たなければならないという欠点があります。

8
Abhishek Amit
import subprocess, threading

class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None

    def run(self, timeout):
        def target():
            print 'Thread started'
            self.process = subprocess.Popen(self.cmd, Shell=True)
            self.process.communicate()
            print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process'
            self.process.terminate()
            thread.join()
        print self.process.returncode

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)

これの出力は次のようになります。

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

ここで、最初の実行ではプロセスが正しく終了し(戻りコード0)、2番目の実行ではプロセスが終了した(戻りコード-15)ことがわかります。

私はWindowsでテストしていません。ただし、サンプルコマンドを更新する以外は、thread.joinまたはprocess.terminateがサポートされていないことを示すドキュメントが見つからないため、機能するはずです。

5
Blairg23

あなたができる

from twisted.internet import reactor, protocol, error, defer

class DyingProcessProtocol(protocol.ProcessProtocol):
    def __init__(self, timeout):
        self.timeout = timeout

    def connectionMade(self):
        @defer.inlineCallbacks
        def killIfAlive():
            try:
                yield self.transport.signalProcess('KILL')
            except error.ProcessExitedAlready:
                pass

        d = reactor.callLater(self.timeout, killIfAlive)

reactor.spawnProcess(DyingProcessProtocol(20), ...)

twistedの非同期プロセスAPIを使用します。

4
Mike Graham

A pythonサブプロセスの自動タイムアウトは組み込まれていないため、独自に構築する必要があります。

これは、python 2.7.3を実行しているUbuntu12.10で動作します

これをtest.pyというファイルに入れます

#!/usr/bin/python
import subprocess
import threading

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

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

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

        if self.is_alive():
            self.p.terminate()   #if your process needs a kill -9 to make 
                                 #it go away, use self.p.kill() here instead.

            self.join()

RunMyCmd(["sleep", "20"], 3).run_the_process()

保存して実行します。

python test.py

sleep 20コマンドの完了には20秒かかります。 3秒以内に終了しない場合(終了しない場合)、プロセスは終了します。

el@apollo:~$  python test.py 
el@apollo:~$ 

プロセスが実行されてから終了するまでに3秒かかります。

3
Eric Leschinski

残念ながら、そのような解決策はありません。タイムアウト後にそれを強制終了するプロセスと一緒に起動するスレッドタイマーを使用してこれを行うことができましたが、ゾンビプロセスなどのためにいくつかの古いファイル記述子の問題に遭遇しました。

2
Noufal Ibrahim

いいえ、タイムアウトはありません。おそらく、あなたが探しているのは、しばらくしてからサブプロセスを強制終了することです。サブプロセスにシグナルを送ることができるので、それを強制終了することもできるはずです。

サブプロセスにシグナルを送信するための一般的なアプローチ:

proc = subprocess.Popen([command])
time.sleep(1)
print 'signaling child'
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)

このメカニズムを使用して、タイムアウト期間後に終了することができます。

2
pyfunc

Python 3.3の時点で、サブプロセスモジュールのブロッキングヘルパー関数へのtimeout引数もあります。

https://docs.python.org/3/library/subprocess.html

1
Mike Graham

はい、 https://pypi.python.org/pypi/python-subprocess2 は、2つの追加機能でPopenモジュールを拡張します。

Popen.waitUpTo(timeout=seconds)

これは、プロセスが完了するまで特定の秒数まで待機します。それ以外の場合は、Noneを返します。

また、

Popen.waitOrTerminate

これは、ある時点まで待機してから、.terminate()、次に.kill()のいずれか、または両方の組み合わせを呼び出します。詳細については、ドキュメントを参照してください。

http://htmlpreview.github.io/?https://github.com/kata198/python-subprocess2/blob/master/doc/subprocess2.html

1
Tim Savannah

Linuxの場合、シグナルを使用できます。これはプラットフォームに依存するため、Windowsには別のソリューションが必要です。ただし、Macでも動作する可能性があります。

def launch_cmd(cmd, timeout=0):
    '''Launch an external command

    It launchs the program redirecting the program's STDIO
    to a communication pipe, and appends those responses to
    a list.  Waits for the program to exit, then returns the
    ouput lines.

    Args:
        cmd: command Line of the external program to launch
        time: time to wait for the command to complete, 0 for indefinitely
    Returns:
        A list of the response lines from the program    
    '''

    import subprocess
    import signal

    class Alarm(Exception):
        pass

    def alarm_handler(signum, frame):
        raise Alarm

    lines = []

    if not launch_cmd.init:
        launch_cmd.init = True
        signal.signal(signal.SIGALRM, alarm_handler)

    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    signal.alarm(timeout)  # timeout sec

    try:
        for line in p.stdout:
            lines.append(line.rstrip())
        p.wait()
        signal.alarm(0)  # disable alarm
    except:
        print "launch_cmd taking too long!"
        p.kill()

    return lines        
launch_cmd.init = False
0
fja0568