【问题标题】:Python subprocess timeout with time consuming tasks具有耗时任务的 Python 子进程超时
【发布时间】:2014-05-17 09:45:36
【问题描述】:

以下代码允许运行具有超时的子进程

p = subprocess.Popen([...])
while timeConsumedSoFar < timeoutLimit
    if proc.poll() is not None:
        doSomething
    else:
        time.sleep(2)
os.kill([...])

它适用于常规的 linux 命令。但是有些命令比较耗时,比如'shred'

'shred [OPTIONS] FILE [...] '

粉碎逻辑卷可能需要 20 多分钟。有没有更好的方法来处理这个问题?

【问题讨论】:

  • 这个问题可能更适合 Stack Overflow 板。无论如何,你想对这个漫长的过程做些什么,让它超时还是让它继续下去?在第一种情况下,您可以使用timeout 命令

标签: linux python process timeout


【解决方案1】:

好像子进程处理等待中断,所以我不能使用它。

这是一个(可能是错误的)示例,它是一种更传统的 unix 方法。 注意 这不会产生任何来自标准输出的输出!事实上,它根本不产生任何输出。

由于所有的 cmets,它看起来比实际的要多。

为什么您可能会认为这更好,因为我们不会不断地对流程进行轮询。相反,我们告诉内核在指定的超时时间后返回给我们如果命令完成。在任何一种情况下,响应都不会从事件发生后延迟超过几微秒,而且代码只需要设置第一个初始轮询。

#!/usr/bin/python

def runwait(timeout, *args):
  from os import fork, execvp, waitpid, kill, dup2
  from signal import alarm, signal, SIGTERM, SIGALRM, SIG_DFL
  from errno import EINTR

  ## We dont do much in the alarm handler, we only care that we interrupted
  ## the wait() on the process.
  def onalarm(sig, frame):
    pass

  ## This is used for redirecting stdin/out/err to /dev/null
  devnull = open('/dev/null', 'r+')
  pid = fork()
  if not pid:
    ## The child process. Begin by redirecting all input and output.
    dup2(devnull.fileno(), 0)
    dup2(devnull.fileno(), 1)
    dup2(devnull.fileno(), 2)
    ## Probably not a great idea to pass this file descriptor to whatever we 
    ## end up executing.
    devnull.close()

    ## Overwrite child process with new execution context.
    execvp(args[0], args)
    ## If something failed (like command not found) exit 63. WARNING, because we
    ## redirected all output to /dev/null, you WILL NOT be informed the command was
    ## not found directly. Use the exit code to work that out.
    sys.exit(63)

  ## This is the parent process that initiated the fork.
  ## Arm a timer using timeout given by first parameter of function. This
  ## must be an int, not a float. I dont bother checking though cause I'm lazy.    
  signal(SIGALRM, onalarm)
  alarm(timeout)

  ## We wait on the pid, this call typically blocks forever.
  try:
    pid, rc = waitpid(pid, 0)
  except OSError as e:
    ## We will land here if the alarm triggered BEFORE the process completed!
    ## In this case, if we were interrupted to deal with the 
    ## signal handler its definitely an alarm. Otherwise
    ## a peripheral exception occurred (permissions for example) so just re-raise the exception.
    if e.errno == EINTR:
      ## We send a TERM signal to terminate the process and re-wait. This causes
      ## wait to (under normal conditions) come back immediately with the signal 
      ## we just killed it with which parse out further down.
      kill(pid, SIGTERM)
      pid, rc = waitpid(pid, 0)
    else:
      raise

  ## Waits status is a 16bit integer packing a 8bit signal and 8bit return code.
  ## Do some funky bitwise operations to separate the two..
  sig = rc & 0xff
  rc >>= 8
  ## Whatever happened above, always disable the alarm and signal handling.
  alarm(0)
  signal(SIGALRM, SIG_DFL)
  return sig, rc


if __name__ == "__main__":
  # An example when you time out
  print runwait(2, "sleep", "20")
  # An example on success
  print runwait(5, "sleep", "3")
  # More success, but demonstrating no output
  print runwait(5, "grep", "root", "/etc/passwd")

函数返回杀死进程的信号号(如果有的话)和执行程序的返回码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-04
    • 2023-01-19
    • 2011-04-13
    • 2014-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多