使用' run '时如何确保子进程在超时时被杀死?



我使用以下代码启动子进程:

# Run the program
subprocess_result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
timeout=timeout,
cwd=directory,
env=env,
preexec_fn=set_memory_limits,
)

启动的子进程也是一个Python程序,带有shebang。此子进程的持续时间可能长于指定的timeout。子进程进行大量计算并将结果写入文件,并且不包含任何信号处理程序。

根据文档https://docs.python.org/3/library/subprocess.html#subprocess.run,subprocess.run杀死超时的孩子:

timeout参数被传递给popen . communication()。如果超时过期时,子进程将被杀死并等待。的TimeoutExpired异常将在子进程发生终止。

当我的子进程超时时,我总是收到subprocess.TimeoutExpired异常,但有时子进程没有被杀死,因此仍然消耗我的机器上的资源。

所以我的问题是,我在这里做错了什么吗?如果是,是什么,如果不是,为什么我有这个问题,我该如何解决?

注意:我在Ubuntu 22_04上使用Python 3.10

您看到的行为最可能的罪魁祸首是您正在生成的子进程可能正在使用多进程并生成自己的子进程。杀死父进程而不是自动杀死所有的后代。子代进程由init进程继承(即PID为1的进程)并将继续运行。

您可以从suprocess.run的源代码进行验证:

with Popen(*popenargs, **kwargs) as process:
try:
stdout, stderr = process.communicate(input, timeout=timeout)
except TimeoutExpired as exc:
process.kill()
if _mswindows:
# Windows accumulates the output in a single blocking
# read() call run on child threads, with the timeout
# being done in a join() on those threads.  communicate()
# _after_ kill() is required to collect that and add it
# to the exception.
exc.stdout, exc.stderr = process.communicate()
else:
# POSIX _communicate already populated the output so
# far into the TimeoutExpired exception.
process.wait()
raise
except:  # Including KeyboardInterrupt, communicate handled that.
process.kill()
# We don't call process.wait() as .__exit__ does that for us.
raise

这里你可以看到在550行communicate调用上设置了超时,如果在552行触发,则子进程是.kill()kill方法发送SIGKILL,立即杀死子进程而不进行任何清理。这是子进程无法捕捉到的信号,所以子进程不可能以某种方式忽略它。

然后在第564行重新引发TimeoutException,因此如果父进程看到此异常,则子进程已经死亡。

但是这里没有提到孙子进程。它们将作为PID 1的子进程继续运行。

我看不出你可以自定义subprocess.run处理子进程终止的方式。例如,如果它使用SIGTERM而不是SIGKILL,您可以修改您的子进程或编写一个包装器进程,它将捕获信号并正确地杀死它的所有后代。但是SIGKILL并没有给你这种奢侈。

所以我相信,对于您的用例,您不能使用subprocess.run外观,但您应该直接使用Popen。你可以看看subprocess.run的实现,只拿你需要的东西,也许放弃对你不使用的平台的支持。


注意:在极其罕见的情况下,子进程不会在SIGKILL时立即死亡。我相信发生这种情况的唯一情况是子进程正在执行非常长的系统调用或其他内核操作,这些操作可能不会立即中断。如果操作处于死锁状态,这可能会阻止进程永远终止。然而,我不认为这是你的情况,因为你没有提到进程被卡住什么也不做,但从你说的进程似乎只是继续运行。

最新更新