|
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