Stop the daemon gracefully. Escalation: StopRequest → SIGTERM → SIGKILL.
()
| 496 | |
| 497 | |
| 498 | def stop_daemon() -> None: |
| 499 | """Stop the daemon gracefully. |
| 500 | |
| 501 | Escalation: StopRequest → SIGTERM → SIGKILL. |
| 502 | """ |
| 503 | global _daemon_ensured # noqa: PLW0603 |
| 504 | _daemon_ensured = False |
| 505 | _surfaced_warnings.clear() |
| 506 | pid_path = daemon_pid_path() |
| 507 | |
| 508 | pid: int | None = None |
| 509 | try: |
| 510 | pid = int(pid_path.read_text().strip()) |
| 511 | if pid == os.getpid(): |
| 512 | pid = None |
| 513 | except (FileNotFoundError, ValueError): |
| 514 | pass |
| 515 | |
| 516 | # 1) Graceful StopRequest via socket (bypass auto-start) |
| 517 | try: |
| 518 | conn = _raw_connect_and_handshake() |
| 519 | try: |
| 520 | conn.send_bytes(encode_request(StopRequest())) |
| 521 | conn.recv_bytes() |
| 522 | finally: |
| 523 | conn.close() |
| 524 | except (ConnectionRefusedError, OSError, RuntimeError, DaemonVersionError): |
| 525 | pass |
| 526 | |
| 527 | if _wait_for_daemon_exit(timeout=3.0): |
| 528 | return |
| 529 | |
| 530 | # 2) SIGTERM |
| 531 | if pid is not None and _pid_alive(pid): |
| 532 | try: |
| 533 | os.kill(pid, signal.SIGTERM) |
| 534 | except (ProcessLookupError, PermissionError): |
| 535 | pass |
| 536 | if _wait_for_daemon_exit(timeout=2.0): |
| 537 | return |
| 538 | |
| 539 | # 3) SIGKILL (Unix) — on Windows SIGTERM already calls TerminateProcess |
| 540 | if sys.platform != "win32" and pid is not None and _pid_alive(pid): |
| 541 | try: |
| 542 | os.kill(pid, signal.SIGKILL) |
| 543 | except (ProcessLookupError, PermissionError): |
| 544 | pass |
| 545 | |
| 546 | _cleanup_stale_files(pid_path, pid) |
| 547 | |
| 548 | |
| 549 | def _cleanup_stale_files(pid_path: Path, pid: int | None) -> None: |