web-dev-qa-db-ja.com

Python、Popen、select-プロセスの終了またはタイムアウトを待機しています

私は次を使用してサブプロセスを実行します:

  p = subprocess.Popen("subprocess", 
                       stdout=subprocess.PIPE, 
                       stderr=subprocess.PIPE, 
                       stdin=subprocess.PIPE)

このサブプロセスは、stderrでエラーが発生してすぐに終了するか、実行を続けます。私はこれらの状態のいずれかを検出したい-後者は数秒待つことによって。

私はこれを試しました:

  SECONDS_TO_WAIT = 10
  select.select([], 
                [p.stdout, p.stderr], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

しかし、それだけを返します:

  ([],[],[])

どちらの条件でも。私に何ができる?

23
George Yuan

Popen.Poll()メソッドを使用してみましたか?あなたはこれを行うことができます:

p = subprocess.Popen("subprocess", 
                   stdout=subprocess.PIPE, 
                   stderr=subprocess.PIPE, 
                   stdin=subprocess.PIPE)

time.sleep(SECONDS_TO_WAIT)
retcode = p.poll()
if retcode is not None:
   # process has terminated

これにより、常に10秒待つことになりますが、失敗のケースがまれである場合、これはすべての成功のケースで償却されます。


編集:

どうですか:

t_nought = time.time()
seconds_passed = 0

while(p.poll() is not None and seconds_passed < 10):
    seconds_passed = time.time() - t_nought

if seconds_passed >= 10:
   #TIMED OUT

これは忙しい待機の醜さを持っていますが、私はそれがあなたが望むものを達成すると思います。

さらに、select callのドキュメントをもう一度見て、次のように変更することをお勧めします。

SECONDS_TO_WAIT = 10
  select.select([p.stderr], 
                [], 
                [p.stdout, p.stderr],
                SECONDS_TO_WAIT)

通常はstderrから読み取る必要があるため、読み取り可能なものがいつあるか(つまり、失敗の場合)を知りたいとします。

これがお役に立てば幸いです。

15
grieve

これは私が思いついたものです。必要なときに機能し、pプロセスでタイムアウトする必要はありませんが、セミビジーループを使用します。

def runCmd(cmd, timeout=None):
    '''
    Will execute a command, read the output and return it back.

    @param cmd: command to execute
    @param timeout: process timeout in seconds
    @return: a Tuple of three: first stdout, then stderr, then exit code
    @raise OSError: on missing command or if a timeout was reached
    '''

    ph_out = None # process output
    ph_err = None # stderr
    ph_ret = None # return code

    p = subprocess.Popen(cmd, Shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    # if timeout is not set wait for process to complete
    if not timeout:
        ph_ret = p.wait()
    else:
        fin_time = time.time() + timeout
        while p.poll() == None and fin_time > time.time():
            time.sleep(1)

        # if timeout reached, raise an exception
        if fin_time < time.time():

            # starting 2.6 subprocess has a kill() method which is preferable
            # p.kill()
            os.kill(p.pid, signal.SIGKILL)
            raise OSError("Process timeout has been reached")

        ph_ret = p.returncode


    ph_out, ph_err = p.communicate()

    return (ph_out, ph_err, ph_ret)
8
Darjus Loktevic

ここに素敵な例があります:

from threading import Timer
from subprocess import Popen, PIPE

def kill_proc():
    proc.kill()

proc = Popen("ping 127.0.0.1", Shell=True)
t = Timer(60, kill_proc)
t.start()
proc.wait()
2
Evan

上記のコメントで述べたように、毎回出力を微調整してコマンドを再実行するだけの場合、次のような作業を行いますか?

from threading import Timer
import subprocess

WAIT_TIME = 10.0

def check_cmd(cmd):
    p = subprocess.Popen(cmd,
        stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE)
    def _check():
        if p.poll()!=0:
            print cmd+" did not quit within the given time period."

    # check whether the given process has exited WAIT_TIME
    # seconds from now
    Timer(WAIT_TIME, _check).start()

check_cmd('echo')
check_cmd('python')

上記のコードを実行すると、以下が出力されます。

python did not quit within the given time period.

上記のコードで考えられる唯一の欠点は、check_cmdを実行し続けるときに重複する可能性があるプロセスです。

1
shsmurfy

Python 3.3

import subprocess as sp

try:
    sp.check_call(["/subprocess"], timeout=10,
                  stdin=sp.DEVNULL, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
except sp.TimeoutError:
    # timeout (the subprocess is killed at this point)
except sp.CalledProcessError:
    # subprocess failed before timeout
else:
    # subprocess ended successfully before timeout

TimeoutExpired docs を参照してください。

1
jfs

これはエヴァンの答えの言い換えですが、以下を考慮しています:

  1. Timerオブジェクトを明示的にキャンセルする:Timerの間隔が長くなり、プロセスが「独自の意志」で終了する場合、これによりスクリプトがハングする可能性があります:(
  2. タイマーアプローチには固有の競合があります(タイマーがプロセスを強制終了しようとすると、直後プロセスが終了し、これがWindowsでは例外が発生します)。

      DEVNULL = open(os.devnull, "wb")
      process = Popen("c:/myExe.exe", stdout=DEVNULL) # no need for stdout
    
      def kill_process():
      """ Kill process helper"""
      try:
         process.kill()
       except OSError:
         pass  # Swallow the error
    
      timer = Timer(timeout_in_sec, kill_process)
      timer.start()
    
      process.wait()
      timer.cancel()
    
0
Shmil The Cat