Python子进程使用超时终止

Python subprocess kill with timeout

我正在使用python中的子进程模块运行一些shell脚本。 如果shell脚本运行得很长,我喜欢杀死子进程。 我认为如果我将timeout=30传递给我的run(..)语句就足够了。

这是代码:

1
2
3
4
5
6
7
8
9
10
11
try:
    result=run(['utilities/shell_scripts/{0} {1} {2}'.format(
                        self.language_conf[key][1], self.proc_dir, config.main_file)],
                shell=True,
                check=True,
                stdout=PIPE,
                stderr=PIPE,
                universal_newlines=True,
                timeout=30,
                bufsize=100)
except TimeoutExpired as timeout:

我用一些运行120s的shell脚本测试了这个调用。 我预计子进程会在30秒后被杀死,但实际上这个过程正在完成120s脚本,而不是引发超时异常。 现在问题如何通过超时杀死子进程?


文档明确指出应该杀死进程:

来自subprocess.run的文档:

"The timeout argument is passed to Popen.communicate(). If the timeout expires, the child process will be killed and waited for. The TimeoutExpired exception will be re-raised after the child process has terminated."

但是在你的情况下,你正在使用shell=True,之前我已经看过类似的问题,因为阻塞过程是shell进程的子进程。

如果你正确地分解你的论点并且你的脚本有适当的shebang,我认为你不需要shell=True。你可以试试这个:

1
2
3
4
5
6
7
8
9
result=run(
  [os.path.join('utilities/shell_scripts',self.language_conf[key][1]), self.proc_dir, config.main_file],  # don't compose argument line yourself
            shell=False,  # no shell wrapper
            check=True,
            stdout=PIPE,
            stderr=PIPE,
            universal_newlines=True,
            timeout=30,
            bufsize=100)

请注意,我可以在Windows上轻松地重现此问题(使用Popen,但它是一样的):

1
2
3
4
5
import subprocess,time

p=subprocess.Popen("notepad",shell=True)
time.sleep(1)
p.kill()

=>记事本保持打开状态,可能是因为它设法从父shell进程中分离出来。

1
2
3
4
5
import subprocess,time

p=subprocess.Popen("notepad",shell=False)
time.sleep(1)
p.kill()

=>记事本在1秒后关闭

有趣的是,如果你删除time.sleep()kill()甚至可以用shell=True工作,因为它成功杀死了正在启动notepad的shell。

我并不是说你有完全相同的问题,我只是在证明shell=True是邪恶的,原因很多,并且无法杀死/超时这个过程是另一个原因。

但是,如果由于某种原因需要shell=True,则可以使用psutil最终杀死所有子项。在这种情况下,最好使用Popen,以便直接获取进程ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess,time,psutil

parent=subprocess.Popen("notepad",shell=True)
for _ in range(30): # 30 seconds
    if parent.poll() is not None:  # process just ended
      break
    time.sleep(1)
else:
   # the for loop ended without break: timeout
   parent = psutil.Process(parent.pid)
   for child in parent.children(recursive=True):  # or parent.children() for recursive=False
       child.kill()
   parent.kill()

(来源:如何从python中杀死进程和子进程?)

该示例也会杀死记事本实例。