【问题标题】:Kill or terminate subprocess when timeout?超时时杀死或终止子进程?
【发布时间】:2011-05-08 16:39:28
【问题描述】:

我想尽可能快地重复执行一个子进程。但是,有时该过程会花费太长时间,因此我想将其杀死。 我使用 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/test2 超时 /bin/sh: /home/lu/workspace/153/squib_driver: not found ---这是下一次执行;程序错误地停止了它。

有谁知道如何解决这个问题?我想及时停止不执行 1 秒 time.sleep(n) 经常等待 n 秒。我不希望它可以执行不到 1 秒

【问题讨论】:

  • 所以基本上如果子进程运行超过 1 秒,你想杀死它并启动下一个?这是正确的吗?
  • 如何创建子流程?因为看起来表达式 ppid=pipeexe.pid 正在获取将要运行的下一个子进程!!!
  • 所以基本上如果子进程运行超过 1 秒,你想杀死它并启动下一个?它是否正确?是的,没错
  • 如何创建子流程? pipeexe=subprocess.Popen(...)
  • 因为看起来表达式 ppid=pipeexe.pid 正在获取将要运行的下一个子进程!我想你可能是对的。但是如何确保下次运行时pid不会改变

标签: python timeout subprocess kill signals


【解决方案1】:

我猜这是线程和进程的面向事件编程中常见的同步问题。

如果您应该始终只运行一个子进程,请确保在运行下一个子进程之前终止当前子进程。否则,信号处理程序可能会获得对最后一个子进程运行的引用并忽略旧的。

假设子进程 A 正在运行。在处理警报信号之前,启动子进程 B。在那之后,您的警报信号处理程序会尝试终止一个子进程。由于当前 PID(或当前子进程管道对象)在启动子进程时设置为 B,B 被杀死,A 继续运行。

我猜对了吗?

为了使您的代码更易于理解,我将在终止当前子进程的部分之后包含创建新子进程的部分。这将清楚地表明,任何时候都只有一个子进程在运行。信号处理程序可以同时执行子进程的终止和启动,就好像它是在循环中运行的迭代块一样,在这种情况下是事件驱动的,每 1 秒发出一次警报信号。

【讨论】:

    【解决方案2】:

    你可以这样做:

    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 秒)时终止它。

    【讨论】:

    • +1。这种方法是我在构建系统中用于测试可执行文件的方法。
    • 也 +1。这似乎是迄今为止我见过的最干净的方法之一。使用 os.kill(self.p.pid, signal.SIGKILL) (或 SIGTERM 后跟 SIGKILL)对 Linux
    • 我粘贴了这段代码,我的命令似乎可以正常启动 - 但是,我需要一次运行一个线程。启动一个进程,让它自然完成,如果时间太长,则终止它,然后重新开始。
    • 能不能从这里得到Popen的返回值?
    • @galois:在Run() 方法执行self.start() 语句后,RunCmd 实例的self.p 包含已创建的subprocess.Popen 实例,因此您可以通过这种方式访问​​它。你为什么要这个?
    【解决方案3】:

    这是我作为子流程执行的看门狗编写的。我现在经常使用它,但我不是那么有经验,所以它可能存在一些缺陷:

    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 模块的文档有点挣扎,以了解subprocess.Popen 没有阻塞;流程是并行执行的(可能我这里用的词不对,但我觉得可以理解)。

    但是由于我写的内容是线性执行的,我真的必须等待命令完成,并设置一个超时时间以避免命令中的错误来暂停脚本的夜间执行。

    【讨论】:

      【解决方案4】:

      这是我使用的:

      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)
      

      【讨论】:

        【解决方案5】:

        稍微复杂一点,我添加了一个answer to solve a similar problem:捕获标准输出,输入标准输入,并且能够在一段时间不活动和/或在整个运行时间之后终止。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-19
          • 2017-07-27
          • 2019-12-09
          • 1970-01-01
          • 1970-01-01
          • 2010-12-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多