unifier-cli 0.5.0

Filesystem postbox for inter-process communication via a Unix tree
Documentation
//! Idle auto-flush and auto-stop for the hot daemon.

use std::time::{Duration, Instant};

/// Seconds of daemon inactivity before an idle flush is considered.
/// Set `UNIFIER_IDLE_FLUSH_SECS=0` to disable.
pub fn idle_after() -> Duration {
    let secs = std::env::var("UNIFIER_IDLE_FLUSH_SECS")
        .ok()
        .and_then(|s| s.parse::<u64>().ok())
        .unwrap_or(5);
    Duration::from_secs(secs)
}

/// Seconds of inactivity before the daemon exits (Gradle-style idle timeout).
/// Default 600 (10 minutes). Set `UNIFIER_IDLE_STOP_SECS=0` to disable.
pub fn idle_stop_after() -> Duration {
    let secs = std::env::var("UNIFIER_IDLE_STOP_SECS")
        .ok()
        .and_then(|s| s.parse::<u64>().ok())
        .unwrap_or(600);
    Duration::from_secs(secs)
}

/// 1-minute load-average ceiling for "no other load".
/// Override with `UNIFIER_IDLE_MAX_LOAD` (default 0.2).
pub fn max_load() -> f64 {
    std::env::var("UNIFIER_IDLE_MAX_LOAD")
        .ok()
        .and_then(|s| s.parse::<f64>().ok())
        .unwrap_or(0.2)
}

pub fn loadavg_1min() -> Option<f64> {
    let text = std::fs::read_to_string("/proc/loadavg").ok()?;
    text.split_whitespace().next()?.parse().ok()
}

pub fn system_is_idle(max: f64) -> bool {
    match loadavg_1min() {
        Some(load) => load <= max,
        None => true,
    }
}

pub fn should_idle_flush(
    last_activity: Instant,
    now: Instant,
    idle_after: Duration,
    load_ok: bool,
    dirty: bool,
    tick_active: bool,
) -> bool {
    if idle_after.is_zero() || !dirty || tick_active || !load_ok {
        return false;
    }
    now.duration_since(last_activity) >= idle_after
}

/// True when the daemon should flush (if needed) and exit after prolonged idle.
/// Skips when event subscribers are connected (e.g. Jan cron) or a tick is active.
pub fn should_idle_stop(
    last_activity: Instant,
    now: Instant,
    idle_stop_after: Duration,
    tick_active: bool,
    event_subscribers: usize,
) -> bool {
    if idle_stop_after.is_zero() || tick_active || event_subscribers > 0 {
        return false;
    }
    now.duration_since(last_activity) >= idle_stop_after
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn waits_for_idle_window() {
        let t0 = Instant::now();
        let t1 = t0 + Duration::from_secs(2);
        assert!(!should_idle_flush(
            t0,
            t1,
            Duration::from_secs(5),
            true,
            true,
            false
        ));
        let t2 = t0 + Duration::from_secs(5);
        assert!(should_idle_flush(
            t0,
            t2,
            Duration::from_secs(5),
            true,
            true,
            false
        ));
    }

    #[test]
    fn parse_loadavg_when_present() {
        if let Some(load) = loadavg_1min() {
            assert!(load >= 0.0);
        }
    }

    #[test]
    fn skips_when_system_busy_or_tick_active_or_clean() {
        let t0 = Instant::now();
        let now = t0 + Duration::from_secs(10);
        let idle = Duration::from_secs(5);
        assert!(!should_idle_flush(t0, now, idle, false, true, false));
        assert!(!should_idle_flush(t0, now, idle, true, true, true));
        assert!(!should_idle_flush(t0, now, idle, true, false, false));
        assert!(!should_idle_flush(
            t0,
            now,
            Duration::ZERO,
            true,
            true,
            false
        ));
    }

    #[test]
    fn idle_stop_requires_quiet_and_no_subscribers() {
        let t0 = Instant::now();
        let now = t0 + Duration::from_secs(600);
        assert!(should_idle_stop(
            t0,
            now,
            Duration::from_secs(600),
            false,
            0
        ));
        assert!(!should_idle_stop(
            t0,
            now,
            Duration::from_secs(600),
            true,
            0
        ));
        assert!(!should_idle_stop(
            t0,
            now,
            Duration::from_secs(600),
            false,
            1
        ));
        assert!(!should_idle_stop(t0, now, Duration::ZERO, false, 0));
        assert!(!should_idle_stop(
            t0,
            t0 + Duration::from_secs(10),
            Duration::from_secs(600),
            false,
            0
        ));
    }
}