lxcrond 0.2.1

cron and entr/inotify server for lxc containers
Documentation

use crate::config::VERBOSE;

use chrono::{Local, Timelike, DateTime};
use std::thread::sleep;
use std::time::Duration;
use std::sync::atomic::Ordering;

/// delay processing of the current thread until we are pretty much on the minute change over.
/// This means when the main thread loops its starts at a minute boundary e.g. 23:23:00  and not 23:23:01
/// Idea is that cron jobs set for `0 3 * * *` start at 03:00:00 and not much later.
///
/// returns the time used to calculate sleep durations, this is important because time might changte between
/// real "now" and when we next compare cron triggers.
pub fn minute_sync() -> DateTime<Local> {
    let mut now = Local::now();
    if now.second() != 0 {
        let mut nanos = now.nanosecond();
        if nanos < 1000000000 {
            nanos = 1000000000 - nanos;
        } else {
            // leap seconds
            nanos = 2000000000 - nanos;
        }
        sleep(Duration::new(59 - now.second() as u64, nanos));
    }
    now = Local::now();
    // in a perfect world we would be @ 00.000000000, but we need to consider anomalies.
    while now.second() >= 59 {
        sleep(Duration::new(1 as u64, 0));
        now = Local::now();
    }
    if now.second() < 30 {
        // good enough
        return now;
    } else {
        // must have hit a significant time jump while booting, try again
        if VERBOSE.load(Ordering::Relaxed) {
            println!("warning wobbly time {}", now.second());
        }
        return minute_sync();
    }

}

/// Sleep for ~60 seconds, correcting for time jumps or slow code.
/// This tends to return a time .0001 - .0003 seconds after the minute on a fast laptop, I dont think
/// this is because the code is slow but because the timer resolutions are not nanosecond specific.
///
/// returns a time that is around (& definatly and greater than) :00
pub fn sleep_sync() -> DateTime<Local>{
    let mut now = Local::now();
    let mut nanos = now.nanosecond();
    if nanos < 1000000000 {
        nanos = 1000000000 - nanos;
    } else {
        // leap seconds
        nanos = 2000000000 - nanos;
    }
    sleep(Duration::new(59 - now.second() as u64, nanos));
    now = Local::now();
    while now.second() >= 59 {
        sleep(Duration::new(1, 0));
        now = Local::now();
    }
    if now.second() < 10 {
        // good enough
    } else {
        if VERBOSE.load(Ordering::Relaxed) {
            println!("warning wobbly time {}", now.second());
        }
        // N.B. we don't try to re-sync because if we jumped to 03:00:30 we should run jobs for 0 3 * * *
    }

    now
}


#[cfg(test)]
mod tests {
//    use super::*;

// test works but takes to long to run always
//    #[test]
//    fn test_ticker() {
//        println!("now = {:?}", Local::now());
//        minute_sync();
//        println!("now = {:?}", Local::now());
//        assert_eq!(0, Local::now().second());
//        sleep_sync();
//        println!("now = {:?}", Local::now());
//        assert_eq!(0, Local::now().second());
//        sleep_sync();
//        println!("now = {:?}", Local::now());
//        assert_eq!(0, Local::now().second());
//
//        panic!() // force panic so intellij shows the logs
//    }

}