在 subprocess.TimeoutExpired 终止子进程
Terminate child process on subprocess.TimeoutExpired
我有以下代码片段:
def terminal_command(command, timeout=5*60):
"""Executes a terminal command."""
cmd = command.split(" ")
timer = time.strftime('%Hh %Mm %Ss', time.gmtime(timeout))
proc = None
try:
proc = subprocess.run(cmd, timeout=timeout, capture_output=True)
except subprocess.TimeoutExpired:
print("Timeout")
proc.terminate()
reason = "timeout"
stdout = b'error'
stderr = b'error'
if proc != None:
# Finished!
stdout = proc.stdout
stderr = proc.stderr
reason = "finished"
return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason
我 运行 一个命令,它花费的时间明显超过 5 分钟。在这种情况下,subprocess.run
引发异常,但 proc
现在是 None
,所以我不能使用 proc.terminate()
。当代码终止时,正如其他地方所记录的那样,子进程继续 运行。我想终止它。
有什么方法可以在 TimeoutExpired
上终止子进程,同时重定向输出?我在 Linux 系统上,所以我愿意接受 Popen
的要求,但理想情况下我希望它是跨平台的。
四个月后:我明白了。
核心问题似乎是将 os.kill
与 signal.SIGKILL
一起使用并不能正确终止进程。
将我的代码修改为以下作品。
def custom_terminal_command(self, command, timeout=5*60, cwd=None):
with subprocess.Popen(command.split(" "), preexec_fn=os.setsid) as process:
wd = os.getcwd()
try:
if cwd is not None:
# Man fuck linux
for d in cwd.split("/"):
os.chdir(d)
stdout, stderr = process.communicate(None, timeout=timeout)
except subprocess.TimeoutExpired as exc:
import signal
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
try:
import msvcrt
except ModuleNotFoundError:
_mswindows = False
else:
_mswindows = True
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()
reason = 'timeout'
stdout, stderr = process.communicate()
except: # Including KeyboardInterrupt, communicate handled that.
process.kill()
# We don't call process.wait() as .__exit__ does that for us.
reason = 'other'
stdout, stderr = process.communicate()
raise
else:
reason = 'finished'
finally:
os.chdir(wd)
try:
return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason
except AttributeError:
try:
return stdout.strip(), stderr.strip(), reason
except AttributeError:
return stdout, stderr, reason
请参阅以下 SO post 进行简短讨论:How to terminate a python subprocess launched with shell=True
我有以下代码片段:
def terminal_command(command, timeout=5*60):
"""Executes a terminal command."""
cmd = command.split(" ")
timer = time.strftime('%Hh %Mm %Ss', time.gmtime(timeout))
proc = None
try:
proc = subprocess.run(cmd, timeout=timeout, capture_output=True)
except subprocess.TimeoutExpired:
print("Timeout")
proc.terminate()
reason = "timeout"
stdout = b'error'
stderr = b'error'
if proc != None:
# Finished!
stdout = proc.stdout
stderr = proc.stderr
reason = "finished"
return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason
我 运行 一个命令,它花费的时间明显超过 5 分钟。在这种情况下,subprocess.run
引发异常,但 proc
现在是 None
,所以我不能使用 proc.terminate()
。当代码终止时,正如其他地方所记录的那样,子进程继续 运行。我想终止它。
有什么方法可以在 TimeoutExpired
上终止子进程,同时重定向输出?我在 Linux 系统上,所以我愿意接受 Popen
的要求,但理想情况下我希望它是跨平台的。
四个月后:我明白了。
核心问题似乎是将 os.kill
与 signal.SIGKILL
一起使用并不能正确终止进程。
将我的代码修改为以下作品。
def custom_terminal_command(self, command, timeout=5*60, cwd=None):
with subprocess.Popen(command.split(" "), preexec_fn=os.setsid) as process:
wd = os.getcwd()
try:
if cwd is not None:
# Man fuck linux
for d in cwd.split("/"):
os.chdir(d)
stdout, stderr = process.communicate(None, timeout=timeout)
except subprocess.TimeoutExpired as exc:
import signal
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
try:
import msvcrt
except ModuleNotFoundError:
_mswindows = False
else:
_mswindows = True
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()
reason = 'timeout'
stdout, stderr = process.communicate()
except: # Including KeyboardInterrupt, communicate handled that.
process.kill()
# We don't call process.wait() as .__exit__ does that for us.
reason = 'other'
stdout, stderr = process.communicate()
raise
else:
reason = 'finished'
finally:
os.chdir(wd)
try:
return stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip(), reason
except AttributeError:
try:
return stdout.strip(), stderr.strip(), reason
except AttributeError:
return stdout, stderr, reason
请参阅以下 SO post 进行简短讨论:How to terminate a python subprocess launched with shell=True