22

I run a subprocess using:

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

This subprocess could either exit immediately with an error on stderr, or keep running. I want to detect either of these conditions - the latter by waiting for several seconds.

I tried this:

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

but it just returns:

  ([],[],[])

on either condition. What can I do?

4

7 回答 7

15

Have you tried using the Popen.Poll() method. You could just do this:

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

This will cause you to always wait 10 seconds, but if the failure case is rare this would be amortized over all the success cases.


Edit:

How about:

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

This has the ugliness of being a busy wait, but I think it accomplishes what you want.

Additionally looking at the select call documentation again I think you may want to change it as follows:

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

Since you would typically want to read from stderr, you want to know when it has something available to read (ie the failure case).

I hope this helps.

于 2008-12-03T17:06:30.480 回答
7

这就是我想出的。当您需要并且不需要在该进程上超时时工作,但有一个半忙循环。

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)
于 2009-06-23T21:58:58.497 回答
3

这是一个很好的例子:

from threading import Timer
from subprocess import Popen, PIPE

proc = Popen("ping 127.0.0.1", shell=True)
t = Timer(60, proc.kill)
t.start()
proc.wait()
于 2011-05-10T20:03:53.160 回答
2

使用select和 sleep 并没有多大意义。 select(或任何内核轮询机制)本质上对异步编程很有用,但您的示例是同步的。因此,要么重写代码以使用正常的阻塞方式,要么考虑使用 Twisted:

from twisted.internet.utils import getProcessOutputAndValue
from twisted.internet import reactor    

def stop(r):
    reactor.stop()
def eb(reason):
    reason.printTraceback()
def cb(result):
    stdout, stderr, exitcode = result
    # do something
getProcessOutputAndValue('/bin/someproc', []
    ).addCallback(cb).addErrback(eb).addBoth(stop)
reactor.run()

顺便说一句,通过编写自己的 ProcessProtocol,使用 Twisted 有一种更安全的方法:

http://twistedmatrix.com/projects/core/documentation/howto/process.html

于 2008-12-03T17:52:16.643 回答
2

蟒蛇 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 文档

于 2012-11-11T20:44:32.060 回答
1

如果,正如您在上面的评论中所说,您只是每次调整输出并重新运行命令,是否会像下面这样工作?

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 时可能会出现重叠的进程。

于 2008-12-04T05:19:41.030 回答
0

这是对埃文的回答的解释,但它考虑了以下几点:

  1. 显式取消 Timer 对象:如果 Timer 间隔很长并且进程将按其“自己的意愿”退出,这可能会挂起您的脚本 :(
  2. Timer 方法中有一个内在的竞争(定时器试图在进程死亡立即终止进程,这在 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()
    
于 2013-03-11T15:36:08.533 回答