//! Whether the machine was suspended (a laptop's lid closed). The monotonic clock every //! timer here runs on (`tokio::time::Instant`: `CLOCK_MONOTONIC` on Linux, likewise //! elsewhere) stops while the machine sleeps, so after eight hours asleep every peer //! looks heard from minutes ago, and the hours count as connected. A clock that runs //! on (`CLOCK_BOOTTIME`, as `/proc/uptime` gives it, on Linux; the wall clock //! elsewhere, which a correction of the clock may move too) tells how long it was: //! what it moved beyond the monotonic clock between two looks. The peer worker looks on //! every pass (`Awake::check`), and on waking ages what judges silence by that much //! (`asleep`). use std::time::{Duration, Instant}; use tracing::warn; use crate::state::AppState; /// Less than this between the clocks is no suspension (a pass that took long, a small /// correction of the wall clock). const AT_LEAST: Duration = Duration::from_secs(30); /// The clocks at the last look. #[derive(Default)] pub(crate) struct Awake { last: Option<(Instant, Duration)>, } impl Awake { /// How long the machine was suspended since the last look, if at least `AT_LEAST`. pub(crate) fn check(&mut self) -> Option { let now = (Instant::now(), running()?); let (monotonic, running) = self.last.replace(now)?; gap( now.0.saturating_duration_since(monotonic), now.1.saturating_sub(running), ) } } /// How long the machine slept between two looks `monotonic` and `running` apart by /// the two clocks, if at least `AT_LEAST`. fn gap(monotonic: Duration, running: Duration) -> Option { let gap = running.saturating_sub(monotonic); (gap >= AT_LEAST).then_some(gap) } /// A clock that runs on while the machine sleeps. fn running() -> Option { #[cfg(target_os = "linux")] { // Seconds since boot, suspension included, with two decimals. let uptime = std::fs::read_to_string("/proc/uptime").ok()?; let seconds: f64 = uptime.split_whitespace().next()?.parse().ok()?; Some(Duration::from_secs_f64(seconds)) } #[cfg(not(target_os = "linux"))] { std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .ok() } } /// The machine slept for `gap`: peers count as silent that much longer (they are held /// dormant, and greeted again, as after an absence that long: they may have moved on), /// and the time counts as not connected (`swarm::stability`). pub(crate) async fn asleep(state: &AppState, gap: Duration) { warn!( secs = gap.as_secs(), "The machine was suspended; peers count as silent for that long" ); crate::swarm::stability::away(state, gap); for (_, peer_state) in state.peers_snapshot().await { let mut peer_state = peer_state.lock().await; peer_state.aged(gap); } } #[cfg(test)] mod tests { use super::*; /// A gap is what the running clock moved beyond the monotonic one, from `AT_LEAST`. #[test] fn a_gap_between_the_clocks_is_a_suspension() { let s = Duration::from_secs; assert_eq!(gap(s(5), s(5)), None); assert_eq!(gap(s(5), s(20)), None); assert_eq!(gap(s(5), s(3605)), Some(s(3600))); // The monotonic clock ahead (paused time in tests): none. assert_eq!(gap(s(3600), s(5)), None); assert!(running().is_some()); } }