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