tokio-walltime 0.1.4

A wallclock time crate for tokio
Documentation
use chrono::{DateTime, Utc};
use errno::errno;
use libc;
use std::{mem::MaybeUninit, ptr};
use thiserror;
use tokio::signal::unix::{signal, SignalKind};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error(transparent)]
    Errno(errno::Errno),
    #[error(transparent)]
    Io(std::io::Error),
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Error::Io(err)
    }
}
impl From<errno::Errno> for Error {
    fn from(err: errno::Errno) -> Self {
        Error::Errno(err)
    }
}

type Result<T> = std::result::Result<T, Error>;

unsafe fn arm_timer(duration: i64) -> Result<libc::timer_t> {
    // First, initialize our timer
    let mut timer: libc::timer_t = std::mem::zeroed();
    // this means we are going to create a SIGALRM
    let mut sev: libc::sigevent = std::mem::zeroed();
    sev.sigev_notify = libc::SIGEV_SIGNAL;
    sev.sigev_signo = SignalKind::alarm().as_raw_value();
    if libc::timer_create(libc::CLOCK_REALTIME, &mut sev, &mut timer) != 0 {
        return Err(Error::from(errno()));
    }

    // Now, get the time to sleep until
    let mut its = libc::itimerspec {
        it_interval: libc::timespec {
            tv_sec: 0,
            tv_nsec: 0,
        },
        it_value: std::mem::zeroed(),
    };
    // by getting the current time
    if libc::clock_gettime(libc::CLOCK_REALTIME, &mut its.it_value) != 0 {
        let err = Err(Error::from(errno()));
        disarm_timer(timer)?;
        return err;
    }

    // and changing the duration
    its.it_value.tv_sec += duration as libc::time_t;

    // Finally, arm the timer
    if libc::timer_settime(timer, libc::TIMER_ABSTIME, &its, ptr::null_mut()) != 0 {
        let err = Err(Error::from(errno()));
        disarm_timer(timer)?;
        return err;
    }

    Ok(timer)
}
unsafe fn disarm_timer(timer: libc::timer_t) -> Result<()> {
    if libc::timer_delete(timer) != 0 {
        return Err(Error::from(errno()));
    }
    Ok(())
}

/// Waits until the specified time.
///
/// `tokio::time::sleep` uses monotonic clock, so if the system is suspended while the timer is
/// active, the timer is delayed by a period equal to the amount of time the system was suspended.
///
/// This timer operates using wall clock as a reference instead. If the system is suspended at the
/// time that the timer would expire, the timer expires immediately after the system resumes from
/// sleep.
///
/// # Errors
///
/// Returns an error if:
///  - Setting a underlying signal handler fails for any reason (see [`signal#errors`]).
///  - Getting the current time (via `clock_gettime(2)`) fails.
///  - Creating the timer (via `timer_create(2)`) fails.
///  - Setting the timer (via `timer_settime(2)`) fails.
///  - Cleaning up the timer after it has triggered (via `timer_delete(2)`) fails.
#[cfg(unix)]
pub async fn sleep_until<Tz: chrono::TimeZone>(time: DateTime<Tz>) -> Result<()> {
    let time = time.with_timezone(&Utc);
    // we must schedule our signal handler before the first signal appears
    let mut alarm = signal(SignalKind::alarm())?;
    loop {
        let currtime = Utc::now();
        let seconds_to_sleep = (time - currtime).num_seconds();
        if seconds_to_sleep < 0 {
            break;
        }
        // now we set a timer for the specified date
        let timer = unsafe { arm_timer(seconds_to_sleep)? };
        // and wait for the signal
        alarm.recv().await;
        unsafe { disarm_timer(timer)? }
    }
    Ok(())
}