check_output()超时本质上是 https://github.com/python/cpython/blob/268c20ef6920651b440f827a93efa8887dcba99f/Lib/subprocess.py#L604-L610:
with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
try:
output, unused_err = process.communicate(inputdata, timeout=timeout)
except TimeoutExpired:
process.kill()
output, unused_err = process.communicate()
raise TimeoutExpired(process.args, timeout, output=output)
有两个问题:
- [第二]
.communicate()
可能会等待后代进程,而不仅仅是直接子进程,请参阅Python 子进程 .check_call 对比
.check_output https://stackoverflow.com/q/36169571/4279
-
process.kill()
可能不会杀死整个进程树,请参阅如何终止使用 shell=True 启动的 python 子进程 https://stackoverflow.com/q/4789837/4279
它导致了您观察到的行为:TimeoutExpired
发生在一秒钟内,外壳被杀死,但是check_output()
孙子后30秒才返回sleep
进程退出。
要解决这些问题,请终止整个进程树(属于同一组的所有子进程):
#!/usr/bin/env python3
import os
import signal
from subprocess import Popen, PIPE, TimeoutExpired
from time import monotonic as timer
start = timer()
with Popen('sleep 30', shell=True, stdout=PIPE, preexec_fn=os.setsid) as process:
try:
output = process.communicate(timeout=1)[0]
except TimeoutExpired:
os.killpg(process.pid, signal.SIGINT) # send signal to the process group
output = process.communicate()[0]
print('Elapsed seconds: {:.2f}'.format(timer() - start))
Output
Elapsed seconds: 1.00