use std::{mem, time::Duration};
use nix::{errno::Errno, sys::signal::Signal, time::ClockId, unistd::gettid};
use crate::{
compat::{Expiration, TimerSetTimeFlags},
fs::{block_signal, unblock_signal},
};
pub struct AlarmTimer {
keep_alive: u64,
timer_id: libc::timer_t,
}
impl AlarmTimer {
pub fn from_seconds(keep_alive: u64) -> Result<Self, Errno> {
Self::from_nanoseconds(keep_alive.checked_mul(1_000_000_000).ok_or(Errno::ERANGE)?)
}
pub fn from_milliseconds(keep_alive: u64) -> Result<Self, Errno> {
Self::from_nanoseconds(keep_alive.checked_mul(1_000_000).ok_or(Errno::ERANGE)?)
}
pub fn from_nanoseconds(keep_alive: u64) -> Result<Self, Errno> {
const SIGEV_THREAD_ID: libc::c_int = 4;
let mut sev: libc::sigevent = unsafe { mem::zeroed() };
sev.sigev_notify = SIGEV_THREAD_ID;
sev.sigev_signo = libc::SIGALRM;
sev.sigev_notify_thread_id = gettid().as_raw();
let mut timer_id: mem::MaybeUninit<libc::timer_t> = mem::MaybeUninit::uninit();
Errno::result(unsafe {
crate::compat::timer_create(
ClockId::CLOCK_MONOTONIC.as_raw(),
std::ptr::addr_of_mut!(sev),
timer_id.as_mut_ptr(),
)
})
.map(|_| {
Self {
keep_alive,
timer_id: unsafe { timer_id.assume_init() },
}
})
}
pub fn start(&mut self) -> Result<(), Errno> {
if self.keep_alive == 0 {
return Ok(());
}
unblock_signal(Signal::SIGALRM)?;
let dur = Duration::from_nanos(self.keep_alive);
let exp = Expiration::OneShot(dur.into());
self.set(exp, TimerSetTimeFlags::empty())
}
pub fn stop(&mut self) -> Result<(), Errno> {
let exp = Expiration::OneShot(Duration::from_secs(0).into());
self.set(exp, TimerSetTimeFlags::empty())?;
block_signal(Signal::SIGALRM)
}
pub fn set_keep_alive(&mut self, keep_alive: u64) {
self.keep_alive = keep_alive;
}
pub fn keep_alive(&self) -> u64 {
self.keep_alive
}
fn set(&mut self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<(), Errno> {
let timerspec: TimerSpec = expiration.into();
Errno::result(unsafe {
crate::compat::timer_settime(
self.timer_id,
flags.bits(),
timerspec.as_ref(),
std::ptr::null_mut(),
)
})
.map(drop)
}
}
impl Drop for AlarmTimer {
fn drop(&mut self) {
#[expect(clippy::disallowed_methods)]
Errno::result(unsafe { crate::compat::timer_delete(self.timer_id) })
.map(drop)
.expect("timer_delete")
}
}
#[expect(clippy::disallowed_types)]
const fn zero_init_timespec() -> libc::timespec {
unsafe { std::mem::transmute([0u8; size_of::<libc::timespec>()]) }
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct TimerSpec(libc::itimerspec);
impl AsMut<libc::itimerspec> for TimerSpec {
fn as_mut(&mut self) -> &mut libc::itimerspec {
&mut self.0
}
}
impl AsRef<libc::itimerspec> for TimerSpec {
fn as_ref(&self) -> &libc::itimerspec {
&self.0
}
}
impl From<Expiration> for TimerSpec {
fn from(expiration: Expiration) -> TimerSpec {
match expiration {
Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
it_interval: zero_init_timespec(),
it_value: *t.as_ref(),
}),
Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
it_interval: *interval.as_ref(),
it_value: *start.as_ref(),
}),
Expiration::Interval(t) => TimerSpec(libc::itimerspec {
it_interval: *t.as_ref(),
it_value: *t.as_ref(),
}),
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn test_timer_zero_init_timespec_1() {
let ts = zero_init_timespec();
assert_eq!(ts.tv_sec, 0);
assert_eq!(ts.tv_nsec, 0);
}
#[test]
fn test_timer_from_1() {
let dur = Duration::from_secs(5);
let ts: TimerSpec = Expiration::OneShot(dur.into()).into();
let inner = ts.as_ref();
assert_eq!(inner.it_value.tv_sec, 5);
assert_eq!(inner.it_value.tv_nsec, 0);
assert_eq!(inner.it_interval.tv_sec, 0);
assert_eq!(inner.it_interval.tv_nsec, 0);
}
#[test]
fn test_timer_from_2() {
let dur = Duration::from_millis(500);
let ts: TimerSpec = Expiration::Interval(dur.into()).into();
let inner = ts.as_ref();
assert_eq!(inner.it_value.tv_sec, 0);
assert_eq!(inner.it_value.tv_nsec, 500_000_000);
assert_eq!(inner.it_interval.tv_sec, 0);
assert_eq!(inner.it_interval.tv_nsec, 500_000_000);
}
#[test]
fn test_timer_from_3() {
let start = Duration::from_secs(1);
let interval = Duration::from_secs(2);
let ts: TimerSpec = Expiration::IntervalDelayed(start.into(), interval.into()).into();
let inner = ts.as_ref();
assert_eq!(inner.it_value.tv_sec, 1);
assert_eq!(inner.it_interval.tv_sec, 2);
}
#[test]
fn test_timer_as_mut_1() {
let dur = Duration::from_secs(1);
let mut ts: TimerSpec = Expiration::OneShot(dur.into()).into();
let inner = ts.as_mut();
inner.it_value.tv_sec = 42;
assert_eq!(ts.as_ref().it_value.tv_sec, 42);
}
#[test]
fn test_timer_from_nanoseconds_1() {
let timer = AlarmTimer::from_nanoseconds(0).unwrap();
assert_eq!(timer.keep_alive(), 0);
}
#[test]
fn test_timer_from_seconds_1() {
let timer = AlarmTimer::from_seconds(1).unwrap();
assert_eq!(timer.keep_alive(), 1_000_000_000);
}
#[test]
fn test_timer_from_milliseconds_1() {
let timer = AlarmTimer::from_milliseconds(500).unwrap();
assert_eq!(timer.keep_alive(), 500_000_000);
}
#[test]
fn test_timer_from_seconds_2() {
match AlarmTimer::from_seconds(u64::MAX) {
Err(e) => assert_eq!(e, nix::errno::Errno::ERANGE),
Ok(_) => panic!("expected ERANGE"),
}
}
#[test]
fn test_timer_from_milliseconds_2() {
match AlarmTimer::from_milliseconds(u64::MAX) {
Err(e) => assert_eq!(e, nix::errno::Errno::ERANGE),
Ok(_) => panic!("expected ERANGE"),
}
}
#[test]
fn test_timer_set_keep_alive_1() {
let mut timer = AlarmTimer::from_nanoseconds(100).unwrap();
assert_eq!(timer.keep_alive(), 100);
timer.set_keep_alive(200);
assert_eq!(timer.keep_alive(), 200);
}
#[test]
fn test_timer_start_1() {
let mut timer = AlarmTimer::from_nanoseconds(0).unwrap();
assert!(timer.start().is_ok());
assert!(timer.stop().is_ok());
}
#[test]
fn test_timer_start_2() {
let mut timer = AlarmTimer::from_seconds(10).unwrap();
assert!(timer.start().is_ok());
assert!(timer.stop().is_ok());
}
#[test]
fn test_timer_drop_1() {
let timer = AlarmTimer::from_nanoseconds(1_000_000).unwrap();
drop(timer);
}
}