//! Background check tasks spawned by `pom serve`, one spawner per check kind. mod backup; mod cors; mod dns; mod health; mod meta_alert; mod prune; mod routes; mod scan_pipeline; mod synckit_fleet; mod systemd; mod tls; mod whois; use tokio_util::sync::CancellationToken; use pom::config::{Config, TargetConfig}; /// The shared cadence of every background check: a fixed interval that skips its /// immediate first tick, delays rather than bursts after a missed one, and yields /// to cancellation without waiting out the remaining interval. /// /// This hands the caller a ticker instead of taking the check body as a callback. /// A callback would have to borrow `pool`/`name`/the target config out of its own /// captures across an await, which needs `AsyncFnMut::CallRefFuture` to name and /// bound as `Send` (unstable). Driving the loop from the caller sidesteps that /// entirely: every borrow stays local to the caller's own async block. pub(crate) struct CheckInterval { interval: tokio::time::Interval, cancel: CancellationToken, startup_tick_pending: bool, } impl CheckInterval { pub(crate) fn new(interval_secs: u64, cancel: CancellationToken) -> Self { let mut interval = tokio::time::interval(std::time::Duration::from_secs(interval_secs)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); Self { interval, cancel, startup_tick_pending: true, } } /// Wait for the next tick. Returns `false` once cancelled, so the call site /// reads `while ticks.next().await { ... }`. /// /// The first call consumes the immediate startup tick, so a check fires one /// full interval after boot rather than the instant the process starts. pub(crate) async fn next(&mut self) -> bool { if self.startup_tick_pending { self.startup_tick_pending = false; self.interval.tick().await; } tokio::select! { () = self.cancel.cancelled() => false, _ = self.interval.tick() => true, } } } /// Every configured target paired with its config, in `target_names` order. /// /// The spawners all opened with the same `for name in config.target_names()` / /// `get_target(&name).unwrap().clone()` pair; the `unwrap` is only sound because /// the name came from the map itself, which this keeps in one place. pub(crate) fn configured_targets( config: &Config, ) -> impl Iterator + use<'_> { config.target_names().into_iter().map(|name| { let target = config .get_target(&name) .expect("name came from target_names") .clone(); (name, target) }) } pub(crate) use backup::spawn_backup_tasks; pub(crate) use cors::spawn_cors_tasks; pub(crate) use dns::spawn_dns_tasks; pub(crate) use health::spawn_health_tasks; pub(crate) use meta_alert::spawn_meta_alert_task; pub(crate) use prune::spawn_prune_task; pub(crate) use routes::spawn_route_tasks; pub(crate) use scan_pipeline::spawn_scan_pipeline_tasks; pub(crate) use synckit_fleet::spawn_synckit_fleet_tasks; pub(crate) use systemd::spawn_systemd_tasks; pub(crate) use tls::spawn_tls_tasks; pub(crate) use whois::spawn_whois_tasks;