|
28 | 28 |
|
29 | 29 | __all__ = ( |
30 | 30 | 'SelectorEventLoop', |
31 | | - 'AbstractChildWatcher', 'SafeChildWatcher', |
32 | | - 'FastChildWatcher', 'PidfdChildWatcher', |
33 | | - 'MultiLoopChildWatcher', 'ThreadedChildWatcher', |
| 31 | + 'AbstractChildWatcher', |
| 32 | + 'PidfdChildWatcher', |
| 33 | + 'ThreadedChildWatcher', |
34 | 34 | 'DefaultEventLoopPolicy', |
35 | 35 | 'EventLoop', |
36 | 36 | ) |
@@ -1062,325 +1062,6 @@ def _sig_chld(self): |
1062 | 1062 | }) |
1063 | 1063 |
|
1064 | 1064 |
|
1065 | | -class SafeChildWatcher(BaseChildWatcher): |
1066 | | - """'Safe' child watcher implementation. |
1067 | | -
|
1068 | | - This implementation avoids disrupting other code spawning processes by |
1069 | | - polling explicitly each process in the SIGCHLD handler instead of calling |
1070 | | - os.waitpid(-1). |
1071 | | -
|
1072 | | - This is a safe solution but it has a significant overhead when handling a |
1073 | | - big number of children (O(n) each time SIGCHLD is raised) |
1074 | | - """ |
1075 | | - |
1076 | | - def __init__(self): |
1077 | | - super().__init__() |
1078 | | - warnings._deprecated("SafeChildWatcher", |
1079 | | - "{name!r} is deprecated as of Python 3.12 and will be " |
1080 | | - "removed in Python {remove}.", |
1081 | | - remove=(3, 14)) |
1082 | | - |
1083 | | - def close(self): |
1084 | | - self._callbacks.clear() |
1085 | | - super().close() |
1086 | | - |
1087 | | - def __enter__(self): |
1088 | | - return self |
1089 | | - |
1090 | | - def __exit__(self, a, b, c): |
1091 | | - pass |
1092 | | - |
1093 | | - def add_child_handler(self, pid, callback, *args): |
1094 | | - self._callbacks[pid] = (callback, args) |
1095 | | - |
1096 | | - # Prevent a race condition in case the child is already terminated. |
1097 | | - self._do_waitpid(pid) |
1098 | | - |
1099 | | - def remove_child_handler(self, pid): |
1100 | | - try: |
1101 | | - del self._callbacks[pid] |
1102 | | - return True |
1103 | | - except KeyError: |
1104 | | - return False |
1105 | | - |
1106 | | - def _do_waitpid_all(self): |
1107 | | - |
1108 | | - for pid in list(self._callbacks): |
1109 | | - self._do_waitpid(pid) |
1110 | | - |
1111 | | - def _do_waitpid(self, expected_pid): |
1112 | | - assert expected_pid > 0 |
1113 | | - |
1114 | | - try: |
1115 | | - pid, status = os.waitpid(expected_pid, os.WNOHANG) |
1116 | | - except ChildProcessError: |
1117 | | - # The child process is already reaped |
1118 | | - # (may happen if waitpid() is called elsewhere). |
1119 | | - pid = expected_pid |
1120 | | - returncode = 255 |
1121 | | - logger.warning( |
1122 | | - "Unknown child process pid %d, will report returncode 255", |
1123 | | - pid) |
1124 | | - else: |
1125 | | - if pid == 0: |
1126 | | - # The child process is still alive. |
1127 | | - return |
1128 | | - |
1129 | | - returncode = waitstatus_to_exitcode(status) |
1130 | | - if self._loop.get_debug(): |
1131 | | - logger.debug('process %s exited with returncode %s', |
1132 | | - expected_pid, returncode) |
1133 | | - |
1134 | | - try: |
1135 | | - callback, args = self._callbacks.pop(pid) |
1136 | | - except KeyError: # pragma: no cover |
1137 | | - # May happen if .remove_child_handler() is called |
1138 | | - # after os.waitpid() returns. |
1139 | | - if self._loop.get_debug(): |
1140 | | - logger.warning("Child watcher got an unexpected pid: %r", |
1141 | | - pid, exc_info=True) |
1142 | | - else: |
1143 | | - callback(pid, returncode, *args) |
1144 | | - |
1145 | | - |
1146 | | -class FastChildWatcher(BaseChildWatcher): |
1147 | | - """'Fast' child watcher implementation. |
1148 | | -
|
1149 | | - This implementation reaps every terminated processes by calling |
1150 | | - os.waitpid(-1) directly, possibly breaking other code spawning processes |
1151 | | - and waiting for their termination. |
1152 | | -
|
1153 | | - There is no noticeable overhead when handling a big number of children |
1154 | | - (O(1) each time a child terminates). |
1155 | | - """ |
1156 | | - def __init__(self): |
1157 | | - super().__init__() |
1158 | | - self._lock = threading.Lock() |
1159 | | - self._zombies = {} |
1160 | | - self._forks = 0 |
1161 | | - warnings._deprecated("FastChildWatcher", |
1162 | | - "{name!r} is deprecated as of Python 3.12 and will be " |
1163 | | - "removed in Python {remove}.", |
1164 | | - remove=(3, 14)) |
1165 | | - |
1166 | | - def close(self): |
1167 | | - self._callbacks.clear() |
1168 | | - self._zombies.clear() |
1169 | | - super().close() |
1170 | | - |
1171 | | - def __enter__(self): |
1172 | | - with self._lock: |
1173 | | - self._forks += 1 |
1174 | | - |
1175 | | - return self |
1176 | | - |
1177 | | - def __exit__(self, a, b, c): |
1178 | | - with self._lock: |
1179 | | - self._forks -= 1 |
1180 | | - |
1181 | | - if self._forks or not self._zombies: |
1182 | | - return |
1183 | | - |
1184 | | - collateral_victims = str(self._zombies) |
1185 | | - self._zombies.clear() |
1186 | | - |
1187 | | - logger.warning( |
1188 | | - "Caught subprocesses termination from unknown pids: %s", |
1189 | | - collateral_victims) |
1190 | | - |
1191 | | - def add_child_handler(self, pid, callback, *args): |
1192 | | - assert self._forks, "Must use the context manager" |
1193 | | - |
1194 | | - with self._lock: |
1195 | | - try: |
1196 | | - returncode = self._zombies.pop(pid) |
1197 | | - except KeyError: |
1198 | | - # The child is running. |
1199 | | - self._callbacks[pid] = callback, args |
1200 | | - return |
1201 | | - |
1202 | | - # The child is dead already. We can fire the callback. |
1203 | | - callback(pid, returncode, *args) |
1204 | | - |
1205 | | - def remove_child_handler(self, pid): |
1206 | | - try: |
1207 | | - del self._callbacks[pid] |
1208 | | - return True |
1209 | | - except KeyError: |
1210 | | - return False |
1211 | | - |
1212 | | - def _do_waitpid_all(self): |
1213 | | - # Because of signal coalescing, we must keep calling waitpid() as |
1214 | | - # long as we're able to reap a child. |
1215 | | - while True: |
1216 | | - try: |
1217 | | - pid, status = os.waitpid(-1, os.WNOHANG) |
1218 | | - except ChildProcessError: |
1219 | | - # No more child processes exist. |
1220 | | - return |
1221 | | - else: |
1222 | | - if pid == 0: |
1223 | | - # A child process is still alive. |
1224 | | - return |
1225 | | - |
1226 | | - returncode = waitstatus_to_exitcode(status) |
1227 | | - |
1228 | | - with self._lock: |
1229 | | - try: |
1230 | | - callback, args = self._callbacks.pop(pid) |
1231 | | - except KeyError: |
1232 | | - # unknown child |
1233 | | - if self._forks: |
1234 | | - # It may not be registered yet. |
1235 | | - self._zombies[pid] = returncode |
1236 | | - if self._loop.get_debug(): |
1237 | | - logger.debug('unknown process %s exited ' |
1238 | | - 'with returncode %s', |
1239 | | - pid, returncode) |
1240 | | - continue |
1241 | | - callback = None |
1242 | | - else: |
1243 | | - if self._loop.get_debug(): |
1244 | | - logger.debug('process %s exited with returncode %s', |
1245 | | - pid, returncode) |
1246 | | - |
1247 | | - if callback is None: |
1248 | | - logger.warning( |
1249 | | - "Caught subprocess termination from unknown pid: " |
1250 | | - "%d -> %d", pid, returncode) |
1251 | | - else: |
1252 | | - callback(pid, returncode, *args) |
1253 | | - |
1254 | | - |
1255 | | -class MultiLoopChildWatcher(AbstractChildWatcher): |
1256 | | - """A watcher that doesn't require running loop in the main thread. |
1257 | | -
|
1258 | | - This implementation registers a SIGCHLD signal handler on |
1259 | | - instantiation (which may conflict with other code that |
1260 | | - install own handler for this signal). |
1261 | | -
|
1262 | | - The solution is safe but it has a significant overhead when |
1263 | | - handling a big number of processes (*O(n)* each time a |
1264 | | - SIGCHLD is received). |
1265 | | - """ |
1266 | | - |
1267 | | - # Implementation note: |
1268 | | - # The class keeps compatibility with AbstractChildWatcher ABC |
1269 | | - # To achieve this it has empty attach_loop() method |
1270 | | - # and doesn't accept explicit loop argument |
1271 | | - # for add_child_handler()/remove_child_handler() |
1272 | | - # but retrieves the current loop by get_running_loop() |
1273 | | - |
1274 | | - def __init__(self): |
1275 | | - self._callbacks = {} |
1276 | | - self._saved_sighandler = None |
1277 | | - warnings._deprecated("MultiLoopChildWatcher", |
1278 | | - "{name!r} is deprecated as of Python 3.12 and will be " |
1279 | | - "removed in Python {remove}.", |
1280 | | - remove=(3, 14)) |
1281 | | - |
1282 | | - def is_active(self): |
1283 | | - return self._saved_sighandler is not None |
1284 | | - |
1285 | | - def close(self): |
1286 | | - self._callbacks.clear() |
1287 | | - if self._saved_sighandler is None: |
1288 | | - return |
1289 | | - |
1290 | | - handler = signal.getsignal(signal.SIGCHLD) |
1291 | | - if handler != self._sig_chld: |
1292 | | - logger.warning("SIGCHLD handler was changed by outside code") |
1293 | | - else: |
1294 | | - signal.signal(signal.SIGCHLD, self._saved_sighandler) |
1295 | | - self._saved_sighandler = None |
1296 | | - |
1297 | | - def __enter__(self): |
1298 | | - return self |
1299 | | - |
1300 | | - def __exit__(self, exc_type, exc_val, exc_tb): |
1301 | | - pass |
1302 | | - |
1303 | | - def add_child_handler(self, pid, callback, *args): |
1304 | | - loop = events.get_running_loop() |
1305 | | - self._callbacks[pid] = (loop, callback, args) |
1306 | | - |
1307 | | - # Prevent a race condition in case the child is already terminated. |
1308 | | - self._do_waitpid(pid) |
1309 | | - |
1310 | | - def remove_child_handler(self, pid): |
1311 | | - try: |
1312 | | - del self._callbacks[pid] |
1313 | | - return True |
1314 | | - except KeyError: |
1315 | | - return False |
1316 | | - |
1317 | | - def attach_loop(self, loop): |
1318 | | - # Don't save the loop but initialize itself if called first time |
1319 | | - # The reason to do it here is that attach_loop() is called from |
1320 | | - # unix policy only for the main thread. |
1321 | | - # Main thread is required for subscription on SIGCHLD signal |
1322 | | - if self._saved_sighandler is not None: |
1323 | | - return |
1324 | | - |
1325 | | - self._saved_sighandler = signal.signal(signal.SIGCHLD, self._sig_chld) |
1326 | | - if self._saved_sighandler is None: |
1327 | | - logger.warning("Previous SIGCHLD handler was set by non-Python code, " |
1328 | | - "restore to default handler on watcher close.") |
1329 | | - self._saved_sighandler = signal.SIG_DFL |
1330 | | - |
1331 | | - # Set SA_RESTART to limit EINTR occurrences. |
1332 | | - signal.siginterrupt(signal.SIGCHLD, False) |
1333 | | - |
1334 | | - def _do_waitpid_all(self): |
1335 | | - for pid in list(self._callbacks): |
1336 | | - self._do_waitpid(pid) |
1337 | | - |
1338 | | - def _do_waitpid(self, expected_pid): |
1339 | | - assert expected_pid > 0 |
1340 | | - |
1341 | | - try: |
1342 | | - pid, status = os.waitpid(expected_pid, os.WNOHANG) |
1343 | | - except ChildProcessError: |
1344 | | - # The child process is already reaped |
1345 | | - # (may happen if waitpid() is called elsewhere). |
1346 | | - pid = expected_pid |
1347 | | - returncode = 255 |
1348 | | - logger.warning( |
1349 | | - "Unknown child process pid %d, will report returncode 255", |
1350 | | - pid) |
1351 | | - debug_log = False |
1352 | | - else: |
1353 | | - if pid == 0: |
1354 | | - # The child process is still alive. |
1355 | | - return |
1356 | | - |
1357 | | - returncode = waitstatus_to_exitcode(status) |
1358 | | - debug_log = True |
1359 | | - try: |
1360 | | - loop, callback, args = self._callbacks.pop(pid) |
1361 | | - except KeyError: # pragma: no cover |
1362 | | - # May happen if .remove_child_handler() is called |
1363 | | - # after os.waitpid() returns. |
1364 | | - logger.warning("Child watcher got an unexpected pid: %r", |
1365 | | - pid, exc_info=True) |
1366 | | - else: |
1367 | | - if loop.is_closed(): |
1368 | | - logger.warning("Loop %r that handles pid %r is closed", loop, pid) |
1369 | | - else: |
1370 | | - if debug_log and loop.get_debug(): |
1371 | | - logger.debug('process %s exited with returncode %s', |
1372 | | - expected_pid, returncode) |
1373 | | - loop.call_soon_threadsafe(callback, pid, returncode, *args) |
1374 | | - |
1375 | | - def _sig_chld(self, signum, frame): |
1376 | | - try: |
1377 | | - self._do_waitpid_all() |
1378 | | - except (SystemExit, KeyboardInterrupt): |
1379 | | - raise |
1380 | | - except BaseException: |
1381 | | - logger.warning('Unknown exception in SIGCHLD handler', exc_info=True) |
1382 | | - |
1383 | | - |
1384 | 1065 | class ThreadedChildWatcher(AbstractChildWatcher): |
1385 | 1066 | """Threaded child watcher implementation. |
1386 | 1067 |
|
|
0 commit comments