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

90 

91 

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 

106 

107 

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 

120 

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) 

125 

126 _kill(pid) 

127 

128 

129def get_exitcodes_terminated_worker(processes): 

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

131 

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

133 exitcode of one terminated worker. 

134 """ 

135 patience = 5 

136 

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) 

151 

152 return _format_exitcodes(exitcodes) 

153 

154 

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) + "}" 

161 

162 

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" 

168 

169 if exitcode < 0: 

170 try: 

171 import signal 

172 

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" 

180 

181 return "UNKNOWN"