|
3 | 3 | import signal |
4 | 4 | import sys |
5 | 5 | import textwrap |
| 6 | +import threading |
6 | 7 | import unittest |
7 | 8 | import warnings |
8 | 9 | from unittest import mock |
@@ -1169,25 +1170,55 @@ def test_stopped_child_does_not_block_event_loop(self): |
1169 | 1170 | # stopped children (si_code == CLD_STOPPED). The child watcher |
1170 | 1171 | # must not mistake this for process termination and attempt to reap |
1171 | 1172 | # the child with waitpid(), which blocks the event loop thread. |
1172 | | - async def run(): |
1173 | | - async with asyncio.timeout(support.SHORT_TIMEOUT): |
| 1173 | + proc = None |
| 1174 | + timed_out = False |
| 1175 | + |
| 1176 | + def watchdog(): |
| 1177 | + nonlocal timed_out |
| 1178 | + timed_out = True |
| 1179 | + # If the event loop gets stuck in waitpid() because of this bug, |
| 1180 | + # killing the child process unblocks waitpid() so the test fails |
| 1181 | + # instead of hanging indefinitely. |
| 1182 | + if proc is not None: |
| 1183 | + if hasattr(signal, 'SIGCONT'): |
| 1184 | + try: |
| 1185 | + os.kill(proc.pid, signal.SIGCONT) |
| 1186 | + except ProcessLookupError: |
| 1187 | + pass |
| 1188 | + try: |
| 1189 | + os.kill(proc.pid, signal.SIGKILL) |
| 1190 | + except ProcessLookupError: |
| 1191 | + pass |
| 1192 | + |
| 1193 | + timer = threading.Timer(support.SHORT_TIMEOUT, watchdog) |
| 1194 | + timer.start() |
| 1195 | + try: |
| 1196 | + async def run(): |
| 1197 | + nonlocal proc |
1174 | 1198 | proc = await asyncio.create_subprocess_exec(*PROGRAM_BLOCKED) |
1175 | 1199 | try: |
1176 | 1200 | proc.send_signal(signal.SIGSTOP) |
1177 | 1201 | # Verify event loop remains responsive while child is stopped |
1178 | 1202 | for _ in range(3): |
1179 | 1203 | await asyncio.sleep(0.05) |
| 1204 | + self.assertFalse(timed_out, "event loop was blocked by stopped child") |
1180 | 1205 | self.assertIsNone(proc.returncode) |
1181 | 1206 | finally: |
1182 | 1207 | if hasattr(signal, 'SIGCONT'): |
1183 | 1208 | try: |
1184 | 1209 | proc.send_signal(signal.SIGCONT) |
1185 | 1210 | except ProcessLookupError: |
1186 | 1211 | pass |
1187 | | - proc.kill() |
| 1212 | + try: |
| 1213 | + proc.kill() |
| 1214 | + except ProcessLookupError: |
| 1215 | + pass |
1188 | 1216 | await proc.wait() |
1189 | 1217 |
|
1190 | | - self.loop.run_until_complete(run()) |
| 1218 | + self.loop.run_until_complete(run()) |
| 1219 | + finally: |
| 1220 | + timer.cancel() |
| 1221 | + timer.join() |
1191 | 1222 |
|
1192 | 1223 | @unittest.skipUnless( |
1193 | 1224 | unix_events.can_use_pidfd(), |
|
0 commit comments