File size: 5,757 Bytes
7885a28 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
import os
import sys
import time
import errno
import signal
import warnings
import subprocess
import traceback
try:
import psutil
except ImportError:
psutil = None
def kill_process_tree(process, use_psutil=True):
"""Terminate process and its descendants with SIGKILL"""
if use_psutil and psutil is not None:
_kill_process_tree_with_psutil(process)
else:
_kill_process_tree_without_psutil(process)
def recursive_terminate(process, use_psutil=True):
warnings.warn(
"recursive_terminate is deprecated in loky 3.2, use kill_process_tree"
"instead",
DeprecationWarning,
)
kill_process_tree(process, use_psutil=use_psutil)
def _kill_process_tree_with_psutil(process):
try:
descendants = psutil.Process(process.pid).children(recursive=True)
except psutil.NoSuchProcess:
return
# Kill the descendants in reverse order to avoid killing the parents before
# the descendant in cases where there are more processes nested.
for descendant in descendants[::-1]:
try:
descendant.kill()
except psutil.NoSuchProcess:
pass
try:
psutil.Process(process.pid).kill()
except psutil.NoSuchProcess:
pass
process.join()
def _kill_process_tree_without_psutil(process):
"""Terminate a process and its descendants."""
try:
if sys.platform == "win32":
_windows_taskkill_process_tree(process.pid)
else:
_posix_recursive_kill(process.pid)
except Exception: # pragma: no cover
details = traceback.format_exc()
warnings.warn(
"Failed to kill subprocesses on this platform. Please install"
"psutil: https://github.com/giampaolo/psutil\n"
f"Details:\n{details}"
)
# In case we cannot introspect or kill the descendants, we fall back to
# only killing the main process.
#
# Note: on Windows, process.kill() is an alias for process.terminate()
# which in turns calls the Win32 API function TerminateProcess().
process.kill()
process.join()
def _windows_taskkill_process_tree(pid):
# On windows, the taskkill function with option `/T` terminate a given
# process pid and its children.
try:
subprocess.check_output(
["taskkill", "/F", "/T", "/PID", str(pid)], stderr=None
)
except subprocess.CalledProcessError as e:
# In Windows, taskkill returns 128, 255 for no process found.
if e.returncode not in [128, 255]:
# Let's raise to let the caller log the error details in a
# warning and only kill the root process.
raise # pragma: no cover
def _kill(pid):
# Not all systems (e.g. Windows) have a SIGKILL, but the C specification
# mandates a SIGTERM signal. While Windows is handled specifically above,
# let's try to be safe for other hypothetic platforms that only have
# SIGTERM without SIGKILL.
kill_signal = getattr(signal, "SIGKILL", signal.SIGTERM)
try:
os.kill(pid, kill_signal)
except OSError as e:
# if OSError is raised with [Errno 3] no such process, the process
# is already terminated, else, raise the error and let the top
# level function raise a warning and retry to kill the process.
if e.errno != errno.ESRCH:
raise # pragma: no cover
def _posix_recursive_kill(pid):
"""Recursively kill the descendants of a process before killing it."""
try:
children_pids = subprocess.check_output(
["pgrep", "-P", str(pid)], stderr=None, text=True
)
except subprocess.CalledProcessError as e:
# `ps` returns 1 when no child process has been found
if e.returncode == 1:
children_pids = ""
else:
raise # pragma: no cover
# Decode the result, split the cpid and remove the trailing line
for cpid in children_pids.splitlines():
cpid = int(cpid)
_posix_recursive_kill(cpid)
_kill(pid)
def get_exitcodes_terminated_worker(processes):
"""Return a formatted string with the exitcodes of terminated workers.
If necessary, wait (up to .25s) for the system to correctly set the
exitcode of one terminated worker.
"""
patience = 5
# Catch the exitcode of the terminated workers. There should at least be
# one. If not, wait a bit for the system to correctly set the exitcode of
# the terminated worker.
exitcodes = [
p.exitcode for p in list(processes.values()) if p.exitcode is not None
]
while not exitcodes and patience > 0:
patience -= 1
exitcodes = [
p.exitcode
for p in list(processes.values())
if p.exitcode is not None
]
time.sleep(0.05)
return _format_exitcodes(exitcodes)
def _format_exitcodes(exitcodes):
"""Format a list of exit code with names of the signals if possible"""
str_exitcodes = [
f"{_get_exitcode_name(e)}({e})" for e in exitcodes if e is not None
]
return "{" + ", ".join(str_exitcodes) + "}"
def _get_exitcode_name(exitcode):
if sys.platform == "win32":
# The exitcode are unreliable on windows (see bpo-31863).
# For this case, return UNKNOWN
return "UNKNOWN"
if exitcode < 0:
try:
import signal
return signal.Signals(-exitcode).name
except ValueError:
return "UNKNOWN"
elif exitcode != 255:
# The exitcode are unreliable on forkserver were 255 is always returned
# (see bpo-30589). For this case, return UNKNOWN
return "EXIT"
return "UNKNOWN"
|