Kill an entire process tree (root + all children). This method MUST be called with self.lock held. Args: info: ProcessTreeInfo containing root and child PIDs Returns: Number of processes killed
(self, info: ProcessTreeInfo)
| 260 | return orphaned_clients |
| 261 | |
| 262 | def _kill_process_tree(self, info: ProcessTreeInfo) -> int: |
| 263 | """Kill an entire process tree (root + all children). |
| 264 | |
| 265 | This method MUST be called with self.lock held. |
| 266 | |
| 267 | Args: |
| 268 | info: ProcessTreeInfo containing root and child PIDs |
| 269 | |
| 270 | Returns: |
| 271 | Number of processes killed |
| 272 | """ |
| 273 | killed_count = 0 |
| 274 | all_pids = info.child_pids + [info.root_pid] |
| 275 | |
| 276 | # Refresh child list one last time before killing |
| 277 | try: |
| 278 | root_proc = psutil.Process(info.root_pid) |
| 279 | children = root_proc.children(recursive=True) |
| 280 | all_pids = [child.pid for child in children] + [info.root_pid] |
| 281 | except KeyboardInterrupt as ki: |
| 282 | handle_keyboard_interrupt(ki) |
| 283 | raise |
| 284 | except Exception: |
| 285 | pass # Use cached PID list |
| 286 | |
| 287 | # Kill children first (bottom-up to avoid orphans) |
| 288 | processes_to_kill: list[psutil.Process] = [] |
| 289 | for pid in reversed(all_pids): # Reverse to kill children before parents |
| 290 | try: |
| 291 | proc = psutil.Process(pid) |
| 292 | processes_to_kill.append(proc) |
| 293 | except psutil.NoSuchProcess: |
| 294 | pass # Already dead |
| 295 | except KeyboardInterrupt as ki: |
| 296 | handle_keyboard_interrupt(ki) |
| 297 | raise |
| 298 | except Exception as e: |
| 299 | logging.warning(f"Failed to get process {pid}: {e}") |
| 300 | |
| 301 | # Terminate all processes |
| 302 | for proc in processes_to_kill: |
| 303 | try: |
| 304 | proc.terminate() |
| 305 | killed_count += 1 |
| 306 | logging.debug(f"Terminated process {proc.pid} ({proc.name()})") |
| 307 | except psutil.NoSuchProcess: |
| 308 | pass # Already dead |
| 309 | except KeyboardInterrupt as ki: |
| 310 | handle_keyboard_interrupt(ki) |
| 311 | raise |
| 312 | except Exception as e: |
| 313 | logging.warning(f"Failed to terminate process {proc.pid}: {e}") |
| 314 | |
| 315 | # Wait for graceful termination |
| 316 | gone, alive = psutil.wait_procs(processes_to_kill, timeout=3) |
| 317 | |
| 318 | # Force kill any stragglers |
| 319 | for proc in alive: |
no test coverage detected