Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/joblib/externals/loky/backend/utils.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

84 statements  

1import os 

2import sys 

3import time 

4import errno 

5import signal 

6import warnings 

7import subprocess 

8import traceback 

9 

10try: 

11 import psutil 

12except ImportError: 

13 psutil = None 

14 

15 

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) 

22 

23 

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) 

31 

32 

33def _kill_process_tree_with_psutil(process): 

34 try: 

35 descendants = psutil.Process(process.pid).children(recursive=True) 

36 except psutil.NoSuchProcess: 

37 return 

38 

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 

46 

47 try: 

48 psutil.Process(process.pid).kill() 

49 except psutil.NoSuchProcess: 

50 pass 

51 process.join() 

52 

53 

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() 

75 

76 

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)], 

83 stderr=None, 

84 creationflags=subprocess.CREATE_NO_WINDOW, 

85 ) 

86 except subprocess.CalledProcessError as e: 

87 # In Windows, taskkill returns 128, 255 for no process found. 

88 if e.returncode not in [128, 255]: 

89 # Let's raise to let the caller log the error details in a 

90 # warning and only kill the root process. 

91 raise # pragma: no cover 

92 

93 

94def _kill(pid): 

95 # Not all systems (e.g. Windows) have a SIGKILL, but the C specification 

96 # mandates a SIGTERM signal. While Windows is handled specifically above, 

97 # let's try to be safe for other hypothetic platforms that only have 

98 # SIGTERM without SIGKILL. 

99 kill_signal = getattr(signal, "SIGKILL", signal.SIGTERM) 

100 try: 

101 os.kill(pid, kill_signal) 

102 except OSError as e: 

103 # if OSError is raised with [Errno 3] no such process, the process 

104 # is already terminated, else, raise the error and let the top 

105 # level function raise a warning and retry to kill the process. 

106 if e.errno != errno.ESRCH: 

107 raise # pragma: no cover 

108 

109 

110def _posix_recursive_kill(pid): 

111 """Recursively kill the descendants of a process before killing it.""" 

112 try: 

113 children_pids = subprocess.check_output( 

114 ["pgrep", "-P", str(pid)], stderr=None, text=True 

115 ) 

116 except subprocess.CalledProcessError as e: 

117 # `ps` returns 1 when no child process has been found 

118 if e.returncode == 1: 

119 children_pids = "" 

120 else: 

121 raise # pragma: no cover 

122 

123 # Decode the result, split the cpid and remove the trailing line 

124 for cpid in children_pids.splitlines(): 

125 cpid = int(cpid) 

126 _posix_recursive_kill(cpid) 

127 

128 _kill(pid) 

129 

130 

131def get_exitcodes_terminated_worker(processes): 

132 """Return a formatted string with the exitcodes of terminated workers. 

133 

134 If necessary, wait (up to .25s) for the system to correctly set the 

135 exitcode of one terminated worker. 

136 """ 

137 patience = 5 

138 

139 # Catch the exitcode of the terminated workers. There should at least be 

140 # one. If not, wait a bit for the system to correctly set the exitcode of 

141 # the terminated worker. 

142 exitcodes = [ 

143 p.exitcode for p in list(processes.values()) if p.exitcode is not None 

144 ] 

145 while not exitcodes and patience > 0: 

146 patience -= 1 

147 exitcodes = [ 

148 p.exitcode 

149 for p in list(processes.values()) 

150 if p.exitcode is not None 

151 ] 

152 time.sleep(0.05) 

153 

154 return _format_exitcodes(exitcodes) 

155 

156 

157def _format_exitcodes(exitcodes): 

158 """Format a list of exit code with names of the signals if possible""" 

159 str_exitcodes = [ 

160 f"{_get_exitcode_name(e)}({e})" for e in exitcodes if e is not None 

161 ] 

162 return "{" + ", ".join(str_exitcodes) + "}" 

163 

164 

165def _get_exitcode_name(exitcode): 

166 if sys.platform == "win32": 

167 # The exitcode are unreliable on windows (see bpo-31863). 

168 # For this case, return UNKNOWN 

169 return "UNKNOWN" 

170 

171 if exitcode < 0: 

172 try: 

173 import signal 

174 

175 return signal.Signals(-exitcode).name 

176 except ValueError: 

177 return "UNKNOWN" 

178 elif exitcode != 255: 

179 # The exitcode are unreliable on forkserver were 255 is always returned 

180 # (see bpo-30589). For this case, return UNKNOWN 

181 return "EXIT" 

182 

183 return "UNKNOWN"