"""GracefulShutdown — orderly router shutdown with tunnel drain. Ported from net.i2p.router.Router (shutdown logic) and net.i2p.router.tasks.GracefulShutdown. Graceful shutdown polls participating tunnel count at intervals. When count reaches 0 (all tunnels expired), the final shutdown proceeds. Hard shutdown waits a short delay then forces immediate stop. """ from __future__ import annotations import asyncio import logging import time logger = logging.getLogger(__name__) class GracefulShutdown: """Manages orderly router shutdown with tunnel drain.""" POLL_INTERVAL_SECONDS = 10 HARD_SHUTDOWN_DELAY_SECONDS = 2 def __init__(self, router) -> None: self._router = router self._shutting_down = False self._shutdown_initiated_at: float | None = None @property def is_shutting_down(self) -> bool: return self._shutting_down def get_participating_count(self) -> int: """Get current participating tunnel count from router context.""" ctx = getattr(self._router, '_context', None) if ctx is None: return 0 tunnel_mgr = getattr(ctx, 'tunnel_manager', None) if tunnel_mgr is None: return 0 return getattr(tunnel_mgr, 'participating_count', 0) async def start_graceful(self) -> None: """Begin graceful shutdown: drain tunnels then stop. Polls participating count every POLL_INTERVAL_SECONDS. Once all tunnels have expired, calls router.shutdown(). """ if self._shutting_down: return self._shutting_down = True self._shutdown_initiated_at = time.monotonic() logger.info("Graceful shutdown initiated, draining tunnels...") while self.get_participating_count() > 0: count = self.get_participating_count() elapsed = time.monotonic() - self._shutdown_initiated_at logger.info( "Graceful shutdown: %d tunnels remaining (%.0fs elapsed)", count, elapsed, ) await asyncio.sleep(self.POLL_INTERVAL_SECONDS) logger.info("All tunnels drained, completing shutdown") self._router.shutdown() async def start_hard(self) -> None: """Immediate shutdown after a short delay.""" if self._shutting_down: return self._shutting_down = True logger.info( "Hard shutdown in %ds...", self.HARD_SHUTDOWN_DELAY_SECONDS, ) await asyncio.sleep(self.HARD_SHUTDOWN_DELAY_SECONDS) self._router.shutdown()