Monitor a single process for being stuck (runs in separate thread)
(self, process: RunningProcess)
| 1292 | return stuck_processes |
| 1293 | |
| 1294 | def _monitor_process(self, process: RunningProcess) -> None: |
| 1295 | """Monitor a single process for being stuck (runs in separate thread)""" |
| 1296 | thread_id = threading.current_thread().ident |
| 1297 | thread_name = threading.current_thread().name |
| 1298 | |
| 1299 | try: |
| 1300 | last_activity_time = time.time() |
| 1301 | |
| 1302 | while not self.shutdown_event.is_set(): |
| 1303 | if process.finished: |
| 1304 | # Process completed normally, stop monitoring |
| 1305 | return |
| 1306 | |
| 1307 | # Check if we have recent stdout activity |
| 1308 | if process.has_pending_output(): |
| 1309 | # Drain pending output to detect ongoing activity |
| 1310 | while process.has_pending_output(): |
| 1311 | process.get_next_line_non_blocking() |
| 1312 | last_activity_time = time.time() |
| 1313 | |
| 1314 | # Check if process is stuck |
| 1315 | current_time = time.time() |
| 1316 | if current_time - last_activity_time > self.stuck_process_timeout: |
| 1317 | # Process is stuck, signal the main thread |
| 1318 | signal = StuckProcessSignal( |
| 1319 | process=process, timeout_duration=self.stuck_process_timeout |
| 1320 | ) |
| 1321 | self.stuck_signals.put(signal) |
| 1322 | return |
| 1323 | |
| 1324 | # Sleep briefly before next check |
| 1325 | time.sleep(1.0) # Check every second |
| 1326 | |
| 1327 | except KeyboardInterrupt as ki: |
| 1328 | handle_keyboard_interrupt(ki) |
| 1329 | except Exception as e: |
| 1330 | # Monitor thread hit an unexpected error (e.g., race with process |
| 1331 | # cleanup). Log it but do NOT interrupt the main thread — this is a |
| 1332 | # monitoring thread, not a critical path. Interrupting main here |
| 1333 | # causes spurious "Interrupt signal received" on normal exits. |
| 1334 | ts_print(f"❌ Thread {thread_id} ({thread_name}) unexpected error: {e}") |
| 1335 | import traceback # noqa: PLC0415 - lazy: only imported on error path |
| 1336 | |
| 1337 | traceback.print_exc() |
| 1338 | |
| 1339 | |
| 1340 | def _handle_stuck_processes( |
nothing calls this directly
no test coverage detected