from __future__ import annotations import asyncio import logging from time import monotonic from .probes import probe_many from .store import Store logger = logging.getLogger(__name__) async def scheduled_probe_loop( store: Store, *, interval_seconds: int, timeout_seconds: float, retries: int, concurrency: int, retention_days: int, ) -> None: next_due: dict[str, float] = {} while True: sleep_seconds = interval_seconds try: # The Explore demo is deliberately synthetic. Pausing scheduled # probes while it is active keeps its seeded incident and history # deterministic and avoids any dependency on external services. if not getattr(store, "demo_active", lambda: False)(): services = store.list_services() now = monotonic() due = [ service for service in services if service.enabled and service.monitoring_enabled and now >= next_due.get(service.id, 0) ] if due: results = await probe_many( due, timeout_seconds=None, retries=None, concurrency=concurrency, trigger="scheduled", default_timeout_seconds=timeout_seconds, default_retries=retries, ) for result in results: store.save_probe(result) for service in due: next_due[service.id] = now + (service.interval_seconds or interval_seconds) next_intervals = [ service.interval_seconds or interval_seconds for service in services if service.enabled and service.monitoring_enabled ] sleep_seconds = min([interval_seconds, *next_intervals]) if next_intervals else interval_seconds else: sleep_seconds = interval_seconds store.prune_probes(retention_days) except asyncio.CancelledError: raise except Exception: # pragma: no cover - defensive daemon boundary logger.exception("Scheduled probe cycle failed") await asyncio.sleep(max(1, sleep_seconds))