Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides functions: get_native_path_case(), isabs() and safe_join(). | 5 """Provides functions: get_native_path_case(), isabs() and safe_join(). |
| 6 | 6 |
| 7 This module assumes that filesystem is not changing while current process | 7 This module assumes that filesystem is not changing while current process |
| 8 is running and thus it caches results of functions that depend on FS state. | 8 is running and thus it caches results of functions that depend on FS state. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 1075 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1086 | 1086 |
| 1087 # First try the soft way: tries 3 times to delete and sleep a bit in between. | 1087 # First try the soft way: tries 3 times to delete and sleep a bit in between. |
| 1088 # Retries help if test subprocesses outlive main process and try to actively | 1088 # Retries help if test subprocesses outlive main process and try to actively |
| 1089 # use or write to the directory while it is being deleted. | 1089 # use or write to the directory while it is being deleted. |
| 1090 max_tries = 3 | 1090 max_tries = 3 |
| 1091 for i in xrange(max_tries): | 1091 for i in xrange(max_tries): |
| 1092 # errors is a list of tuple(function, path, excinfo). | 1092 # errors is a list of tuple(function, path, excinfo). |
| 1093 errors = [] | 1093 errors = [] |
| 1094 fs.rmtree(root, onerror=lambda *args: errors.append(args)) | 1094 fs.rmtree(root, onerror=lambda *args: errors.append(args)) |
| 1095 if not errors: | 1095 if not errors: |
| 1096 if i: | |
| 1097 sys.stderr.write('Succeeded.\n') | |
| 1096 return True | 1098 return True |
| 1097 if not i and sys.platform == 'win32': | 1099 if not i and sys.platform == 'win32': |
| 1098 for _, path, _ in errors: | 1100 for path in sorted(set(path for _, path, _ in errors)): |
| 1099 try: | 1101 try: |
| 1100 change_acl_for_delete(path) | 1102 change_acl_for_delete(path) |
| 1101 except Exception as e: | 1103 except Exception as e: |
| 1102 sys.stderr.write('- %s (failed to update ACL: %s)\n' % (path, e)) | 1104 sys.stderr.write('- %s (failed to update ACL: %s)\n' % (path, e)) |
| 1103 | 1105 |
| 1104 if i == max_tries - 1: | 1106 if i != max_tries - 1: |
| 1105 sys.stderr.write( | |
| 1106 'Failed to delete %s. The following files remain:\n' % root) | |
| 1107 for _, path, _ in errors: | |
| 1108 sys.stderr.write('- %s\n' % path) | |
| 1109 else: | |
| 1110 delay = (i+1)*2 | 1107 delay = (i+1)*2 |
| 1111 sys.stderr.write( | 1108 sys.stderr.write( |
| 1112 'Failed to delete %s (%d files remaining).\n' | 1109 'Failed to delete %s (%d files remaining).\n' |
| 1113 ' Maybe the test has a subprocess outliving it.\n' | 1110 ' Maybe the test has a subprocess outliving it.\n' |
| 1114 ' Sleeping %d seconds.\n' % | 1111 ' Sleeping %d seconds.\n' % |
| 1115 (root, len(errors), delay)) | 1112 (root, len(errors), delay)) |
| 1116 time.sleep(delay) | 1113 time.sleep(delay) |
| 1117 | 1114 |
| 1115 sys.stderr.write( | |
|
Vadim Sh.
2017/06/16 22:18:10
please add fs.exists(root) here too and ignore any
M-A Ruel
2017/06/17 00:42:57
added at line 1095 instead.
| |
| 1116 'Failed to delete %s. The following files remain:\n' % root) | |
| 1117 # The same path may be listed multiple times. | |
| 1118 for path in sorted(set(path for _, path, _ in errors)): | |
| 1119 sys.stderr.write('- %s\n' % path) | |
| 1120 | |
| 1118 # If soft retries fail on Linux, there's nothing better we can do. | 1121 # If soft retries fail on Linux, there's nothing better we can do. |
| 1119 if sys.platform != 'win32': | 1122 if sys.platform != 'win32': |
| 1120 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1123 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1121 | 1124 |
| 1122 # The soft way was not good enough. Try the hard way. Enumerates both: | 1125 # The soft way was not good enough. Try the hard way. |
| 1123 # - all child processes from this process. | 1126 for i in xrange(max_tries): |
| 1124 # - processes where the main executable in inside 'root'. The reason is that | 1127 if not _kill_children_processes_win(root): |
| 1125 # the ancestry may be broken so stray grand-children processes could be | |
| 1126 # undetected by the first technique. | |
| 1127 # This technique is not fool-proof but gets mostly there. | |
| 1128 def get_processes(): | |
| 1129 processes = enum_processes_win() | |
| 1130 tree_processes = filter_processes_tree_win(processes) | |
| 1131 dir_processes = filter_processes_dir_win(processes, root) | |
| 1132 # Convert to dict to remove duplicates. | |
| 1133 processes = dict((p.ProcessId, p) for p in tree_processes) | |
| 1134 processes.update((p.ProcessId, p) for p in dir_processes) | |
| 1135 processes.pop(os.getpid()) | |
| 1136 return processes | |
| 1137 | |
| 1138 for i in xrange(3): | |
| 1139 sys.stderr.write('Enumerating processes:\n') | |
| 1140 processes = get_processes() | |
| 1141 if not processes: | |
| 1142 break | 1128 break |
| 1143 for _, proc in sorted(processes.iteritems()): | 1129 if i != max_tries - 1: |
| 1144 sys.stderr.write( | |
| 1145 '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % ( | |
| 1146 proc.ProcessId, | |
| 1147 proc.HandleCount, | |
| 1148 proc.ExecutablePath, | |
| 1149 proc.CommandLine)) | |
| 1150 sys.stderr.write('Terminating %d processes.\n' % len(processes)) | |
| 1151 for pid in sorted(processes): | |
| 1152 try: | |
| 1153 # Killing is asynchronous. | |
| 1154 os.kill(pid, 9) | |
| 1155 sys.stderr.write('- %d killed\n' % pid) | |
| 1156 except OSError: | |
| 1157 sys.stderr.write('- failed to kill %s\n' % pid) | |
| 1158 if i < 2: | |
| 1159 time.sleep((i+1)*2) | 1130 time.sleep((i+1)*2) |
| 1160 else: | 1131 else: |
| 1161 processes = get_processes() | 1132 processes = _get_children_processes_win(root) |
| 1162 if processes: | 1133 if processes: |
| 1163 sys.stderr.write('Failed to terminate processes.\n') | 1134 sys.stderr.write('Failed to terminate processes.\n') |
| 1164 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1135 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1165 | 1136 |
| 1166 # Now that annoying processes in root are evicted, try again. | 1137 # Now that annoying processes in root are evicted, try again. |
| 1167 errors = [] | 1138 errors = [] |
| 1168 fs.rmtree(root, onerror=lambda *args: errors.append(args)) | 1139 fs.rmtree(root, onerror=lambda *args: errors.append(args)) |
| 1169 if errors: | 1140 if errors and fs.exists(root): |
| 1170 # There's no hope. | 1141 # There's no hope: the directory was tried to be removed 4 times. Give up |
| 1142 # and raise an exception. | |
| 1171 sys.stderr.write( | 1143 sys.stderr.write( |
| 1172 'Failed to delete %s. The following files remain:\n' % root) | 1144 'Failed to delete %s. The following files remain:\n' % root) |
| 1173 for _, path, _ in errors: | 1145 # The same path may be listed multiple times. |
| 1146 for path in sorted(set(path for _, path, _ in errors)): | |
| 1174 sys.stderr.write('- %s\n' % path) | 1147 sys.stderr.write('- %s\n' % path) |
| 1175 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1148 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1176 return False | 1149 return False |
| 1150 | |
| 1151 | |
| 1152 ## Private code. | |
| 1153 | |
| 1154 | |
| 1155 def _kill_children_processes_win(root): | |
| 1156 """Try to kill all children processes indistriminately and prints updates to | |
| 1157 stderr. | |
| 1158 | |
| 1159 Returns: | |
| 1160 True if at least one child process was found. | |
| 1161 """ | |
| 1162 processes = _get_children_processes_win(root) | |
| 1163 if not processes: | |
| 1164 return False | |
| 1165 sys.stderr.write('Enumerating processes:\n') | |
| 1166 for _, proc in sorted(processes.iteritems()): | |
| 1167 sys.stderr.write( | |
| 1168 '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % ( | |
| 1169 proc.ProcessId, | |
| 1170 proc.HandleCount, | |
| 1171 proc.ExecutablePath, | |
| 1172 proc.CommandLine)) | |
| 1173 sys.stderr.write('Terminating %d processes:\n' % len(processes)) | |
| 1174 for pid in sorted(processes): | |
| 1175 try: | |
| 1176 # Killing is asynchronous. | |
| 1177 os.kill(pid, 9) | |
| 1178 sys.stderr.write('- %d killed\n' % pid) | |
| 1179 except OSError: | |
| 1180 sys.stderr.write('- failed to kill %s\n' % pid) | |
| 1181 return True | |
| 1182 | |
| 1183 | |
| 1184 def _get_children_processes_win(root): | |
| 1185 """Returns a list of processes. | |
| 1186 | |
| 1187 Enumerates both: | |
| 1188 - all child processes from this process. | |
| 1189 - processes where the main executable in inside 'root'. The reason is that | |
| 1190 the ancestry may be broken so stray grand-children processes could be | |
| 1191 undetected by the first technique. | |
| 1192 | |
| 1193 This technique is not fool-proof but gets mostly there. | |
| 1194 """ | |
| 1195 processes = enum_processes_win() | |
| 1196 tree_processes = filter_processes_tree_win(processes) | |
| 1197 dir_processes = filter_processes_dir_win(processes, root) | |
| 1198 # Convert to dict to remove duplicates. | |
| 1199 processes = dict((p.ProcessId, p) for p in tree_processes) | |
| 1200 processes.update((p.ProcessId, p) for p in dir_processes) | |
| 1201 processes.pop(os.getpid()) | |
| 1202 return processes | |
| OLD | NEW |