tokio_walltime/
lib.rs

1use chrono::{DateTime, Utc};
2use errno::errno;
3use libc;
4use std::{mem::MaybeUninit, ptr};
5use thiserror;
6use tokio::signal::unix::{signal, SignalKind};
7
8#[derive(thiserror::Error, Debug)]
9pub enum Error {
10    #[error(transparent)]
11    Errno(errno::Errno),
12    #[error(transparent)]
13    Io(std::io::Error),
14}
15
16impl From<std::io::Error> for Error {
17    fn from(err: std::io::Error) -> Self {
18        Error::Io(err)
19    }
20}
21impl From<errno::Errno> for Error {
22    fn from(err: errno::Errno) -> Self {
23        Error::Errno(err)
24    }
25}
26
27type Result<T> = std::result::Result<T, Error>;
28
29unsafe fn arm_timer(duration: i64) -> Result<libc::timer_t> {
30    // First, initialize our timer
31    let mut timer: libc::timer_t = std::mem::zeroed();
32    // this means we are going to create a SIGALRM
33    let mut sev: libc::sigevent = std::mem::zeroed();
34    sev.sigev_notify = libc::SIGEV_SIGNAL;
35    sev.sigev_signo = SignalKind::alarm().as_raw_value();
36    if libc::timer_create(libc::CLOCK_REALTIME, &mut sev, &mut timer) != 0 {
37        return Err(Error::from(errno()));
38    }
39
40    // Now, get the time to sleep until
41    let mut its = libc::itimerspec {
42        it_interval: libc::timespec {
43            tv_sec: 0,
44            tv_nsec: 0,
45        },
46        it_value: std::mem::zeroed(),
47    };
48    // by getting the current time
49    if libc::clock_gettime(libc::CLOCK_REALTIME, &mut its.it_value) != 0 {
50        let err = Err(Error::from(errno()));
51        disarm_timer(timer)?;
52        return err;
53    }
54
55    // and changing the duration
56    its.it_value.tv_sec += duration as libc::time_t;
57
58    // Finally, arm the timer
59    if libc::timer_settime(timer, libc::TIMER_ABSTIME, &its, ptr::null_mut()) != 0 {
60        let err = Err(Error::from(errno()));
61        disarm_timer(timer)?;
62        return err;
63    }
64
65    Ok(timer)
66}
67unsafe fn disarm_timer(timer: libc::timer_t) -> Result<()> {
68    if libc::timer_delete(timer) != 0 {
69        return Err(Error::from(errno()));
70    }
71    Ok(())
72}
73
74/// Waits until the specified time.
75///
76/// `tokio::time::sleep` uses monotonic clock, so if the system is suspended while the timer is
77/// active, the timer is delayed by a period equal to the amount of time the system was suspended.
78///
79/// This timer operates using wall clock as a reference instead. If the system is suspended at the
80/// time that the timer would expire, the timer expires immediately after the system resumes from
81/// sleep.
82///
83/// # Errors
84///
85/// Returns an error if:
86///  - Setting a underlying signal handler fails for any reason (see [`signal#errors`]).
87///  - Getting the current time (via `clock_gettime(2)`) fails.
88///  - Creating the timer (via `timer_create(2)`) fails.
89///  - Setting the timer (via `timer_settime(2)`) fails.
90///  - Cleaning up the timer after it has triggered (via `timer_delete(2)`) fails.
91#[cfg(unix)]
92pub async fn sleep_until<Tz: chrono::TimeZone>(time: DateTime<Tz>) -> Result<()> {
93    let time = time.with_timezone(&Utc);
94    // we must schedule our signal handler before the first signal appears
95    let mut alarm = signal(SignalKind::alarm())?;
96    loop {
97        let currtime = Utc::now();
98        let seconds_to_sleep = (time - currtime).num_seconds();
99        if seconds_to_sleep < 0 {
100            break;
101        }
102        // now we set a timer for the specified date
103        let timer = unsafe { arm_timer(seconds_to_sleep)? };
104        // and wait for the signal
105        alarm.recv().await;
106        unsafe { disarm_timer(timer)? }
107    }
108    Ok(())
109}