| 247 | return self.captured_ws_endpoint |
| 248 | |
| 249 | def cleanup(self): |
| 250 | logger.info("--- Starting cleanup procedure (CamoufoxProcessManager) ---") |
| 251 | if self.camoufox_proc and self.camoufox_proc.poll() is None: |
| 252 | pid = self.camoufox_proc.pid |
| 253 | logger.info(f"Terminating Camoufox process tree (PID: {pid})...") |
| 254 | try: |
| 255 | if sys.platform == "win32": |
| 256 | # Windows: Force terminate directly, don't try graceful shutdown (headless browsers often hang) |
| 257 | try: |
| 258 | subprocess.run( |
| 259 | ["taskkill", "/F", "/T", "/PID", str(pid)], |
| 260 | capture_output=True, |
| 261 | text=True, |
| 262 | check=False, |
| 263 | timeout=5, |
| 264 | ) |
| 265 | logger.info("Process tree successfully terminated.") |
| 266 | except subprocess.TimeoutExpired: |
| 267 | logger.warning( |
| 268 | "taskkill timed out, process may have terminated." |
| 269 | ) |
| 270 | except Exception as e: |
| 271 | logger.warning(f"taskkill execution exception: {e}") |
| 272 | elif hasattr(os, "getpgid") and hasattr(os, "killpg"): |
| 273 | # Unix: Try SIGTERM, then SIGKILL on timeout |
| 274 | try: |
| 275 | pgid = os.getpgid(pid) |
| 276 | os.killpg(pgid, signal.SIGTERM) |
| 277 | self.camoufox_proc.wait(timeout=5) |
| 278 | logger.info( |
| 279 | f"Process group (PGID: {pgid}) successfully terminated via SIGTERM." |
| 280 | ) |
| 281 | except subprocess.TimeoutExpired: |
| 282 | logger.warning("SIGTERM timed out, sending SIGKILL...") |
| 283 | try: |
| 284 | os.killpg(os.getpgid(pid), signal.SIGKILL) |
| 285 | self.camoufox_proc.wait(timeout=2) |
| 286 | logger.info( |
| 287 | "Process group successfully terminated via SIGKILL." |
| 288 | ) |
| 289 | except Exception: |
| 290 | pass |
| 291 | except ProcessLookupError: |
| 292 | logger.info("Process group not found, may have already exited.") |
| 293 | else: |
| 294 | # Fallback: Terminate process directly |
| 295 | self.camoufox_proc.terminate() |
| 296 | try: |
| 297 | self.camoufox_proc.wait(timeout=5) |
| 298 | logger.info("Process successfully terminated.") |
| 299 | except subprocess.TimeoutExpired: |
| 300 | self.camoufox_proc.kill() |
| 301 | logger.info("Process forcefully terminated.") |
| 302 | except Exception as e_term: |
| 303 | logger.warning(f"Error terminating process: {e_term}") |
| 304 | finally: |
| 305 | # Clean up streams |
| 306 | for stream in [self.camoufox_proc.stdout, self.camoufox_proc.stderr]: |