//! POSIX per-process timer interface. //! //! This module provides a wrapper around POSIX timers (see `timer_create(2)`) and utilities to //! setup thread-targeted signaling and signal masks. use std::mem::MaybeUninit; use std::time::Duration; use std::{io, mem}; use libc::{pid_t, clockid_t, c_int}; /// Timers can use various clocks. See `timer_create(2)`. pub enum Clock { /// Use `CLOCK_REALTIME` for the timer. Realtime, /// Use `CLOCK_MONOTONIC` for the timer. Monotonic, } /// Strong thread-id type to prevent accidental conversion of pid_t. pub struct Tid(pid_t); /// Convenience helper to get the current thread ID suitable to pass to a /// `TimerEvent::ThreadSignal` entry. pub fn gettid() -> Tid { Tid(unsafe { libc::syscall(libc::SYS_gettid) } as pid_t) } /// Strong signal type which is more advanced than nix::sys::signal::Signal as /// it doesn't prevent you from using signals that the nix crate is unaware /// of...! pub struct Signal(c_int); impl Into for Signal { fn into(self) -> c_int { self.0 } } impl From for Signal { fn from(v: c_int) -> Signal { Signal(v) } } /// When instantiating a Timer, it needs to have an event type associated with /// it to be fired whenever the timer expires. Most of the time this will be a /// `Signal`. Sometimes we need to be able to send signals to specific threads. pub enum TimerEvent { /// This will act like passing `NULL` to `timer_create()`, which maps to /// using the same as `Signal(SIGALRM)`. None, /// When the timer expires, send a specific signal to the current process. Signal(Signal), /// When the timer expires, send a specific signal to a specific thread. ThreadSignal(Tid, Signal), /// Convenience value to send a signal to the current thread. This is /// equivalent to using `ThreadSignal(gettid(), signal)`. ThisThreadSignal(Signal), } // timer_t is a pointer type, so we create a strongly typed internal handle // type for it #[repr(C)] struct InternalTimerT(u32); type TimerT = *mut InternalTimerT; // These wrappers are defined in -lrt. #[link(name="rt")] extern "C" { fn timer_create( clockid: clockid_t, evp: *mut libc::sigevent, timer: *mut TimerT, ) -> c_int; fn timer_delete(timer: TimerT) -> c_int; fn timer_settime( timerid: TimerT, flags: c_int, new_value: *const libc::itimerspec, old_value: *mut libc::itimerspec, ) -> c_int; } /// Represents a POSIX per-process timer as created via `timer_create(2)`. pub struct Timer { timer: TimerT, } /// Timer specification used to arm a `Timer`. pub struct TimerSpec { /// The timeout to the next timer event. pub value: Option, /// When a timer expires, it may be automatically rearmed with another /// timeout. This will keep happening until this is explicitly disabled /// or the timer deleted. pub interval: Option, } // Helpers to convert between libc::timespec and Option fn opt_duration_to_timespec(v: Option) -> libc::timespec { match v { None => libc::timespec { tv_sec: 0, tv_nsec: 0, }, Some(value) => libc::timespec { tv_sec: value.as_secs() as i64, tv_nsec: value.subsec_nanos() as i64, }, } } fn timespec_to_opt_duration(v: libc::timespec) -> Option { if v.tv_sec == 0 && v.tv_nsec == 0 { None } else { Some(Duration::new(v.tv_sec as u64, v.tv_nsec as u32)) } } impl TimerSpec { // Helpers to convert between TimerSpec and libc::itimerspec fn to_itimerspec(&self) -> libc::itimerspec { libc::itimerspec { it_value: opt_duration_to_timespec(self.value), it_interval: opt_duration_to_timespec(self.interval), } } fn from_itimerspec(ts: libc::itimerspec) -> Self { TimerSpec { value: timespec_to_opt_duration(ts.it_value), interval: timespec_to_opt_duration(ts.it_interval), } } /// Create an empty timer specification representing a disabled timer. pub fn new() -> Self { TimerSpec { value: None, interval: None } } /// Change the specification to have a specific value. pub fn value(self, value: Option) -> Self { TimerSpec { value, interval: self.interval } } /// Change the specification to have a specific interval. pub fn interval(self, interval: Option) -> Self { TimerSpec { value: self.value, interval } } } impl Timer { /// Create a Timer object governing a POSIX timer. pub fn create(clock: Clock, event: TimerEvent) -> io::Result { // Map from our clock type to the libc id let clkid = match clock { Clock::Realtime => libc::CLOCK_REALTIME, Clock::Monotonic => libc::CLOCK_MONOTONIC, } as clockid_t; // Map the TimerEvent to libc::sigevent let mut ev: libc::sigevent = unsafe { mem::zeroed() }; match event { TimerEvent::None => ev.sigev_notify = libc::SIGEV_NONE, TimerEvent::Signal(signo) => { ev.sigev_signo = signo.0; ev.sigev_notify = libc::SIGEV_SIGNAL; } TimerEvent::ThreadSignal(tid, signo) => { ev.sigev_signo = signo.0; ev.sigev_notify = libc::SIGEV_THREAD_ID; ev.sigev_notify_thread_id = tid.0; } TimerEvent::ThisThreadSignal(signo) => { ev.sigev_signo = signo.0; ev.sigev_notify = libc::SIGEV_THREAD_ID; ev.sigev_notify_thread_id = gettid().0; } } // Create the timer let mut timer: TimerT = unsafe { mem::zeroed() }; let rc = unsafe { timer_create(clkid, &mut ev, &mut timer) }; if rc != 0 { Err(io::Error::last_os_error()) } else { Ok(Timer { timer }) } } /// Arm a timer. This returns the previous timer specification. pub fn arm(&mut self, spec: TimerSpec) -> io::Result { let newspec = spec.to_itimerspec(); let mut oldspec = MaybeUninit::::uninit(); let rc = unsafe { timer_settime(self.timer, 0, &newspec, &mut *oldspec.as_mut_ptr()) }; if rc != 0 { return Err(io::Error::last_os_error()); } Ok(TimerSpec::from_itimerspec(unsafe { oldspec.assume_init() })) } } impl Drop for Timer { fn drop(&mut self) { unsafe { timer_delete(self.timer); } } } /// This is the signal number we use in our timeout implementations. We expect /// the signal handler for this signal to never be replaced by some other /// library. If this does happen, we need to find another signal. There should /// be plenty. /// Currently this is SIGRTMIN+4, the 5th real-time signal. glibc reserves the /// first two for pthread internals. pub const SIGTIMEOUT: Signal = Signal(32 + 4); // Our timeout handler does exactly nothing. We only need it to interrupt // system calls. extern "C" fn sig_timeout_handler(_: c_int) { } // See setup_timeout_handler(). fn do_setup_timeout_handler() -> io::Result<()> { // Unfortunately nix::sys::signal::Signal cannot represent real time // signals, so we need to use libc instead... // // This WOULD be a nicer impl though: //nix::sys::signal::sigaction( // SIGTIMEOUT, // nix::sys::signal::SigAction::new( // nix::sys::signal::SigHandler::Handler(sig_timeout_handler), // nix::sys::signal::SaFlags::empty(), // nix::sys::signal::SigSet::all())) // .map(|_|()) unsafe { let mut sa_mask = MaybeUninit::::uninit(); if libc::sigemptyset(&mut *sa_mask.as_mut_ptr()) != 0 || libc::sigaddset(&mut *sa_mask.as_mut_ptr(), SIGTIMEOUT.0) != 0 { return Err(io::Error::last_os_error()); } let sa = libc::sigaction { sa_sigaction: // libc::sigaction uses `usize` for the function pointer... sig_timeout_handler as *const extern "C" fn(i32) as usize, sa_mask: sa_mask.assume_init(), sa_flags: 0, sa_restorer: None, }; if libc::sigaction(SIGTIMEOUT.0, &sa, std::ptr::null_mut()) != 0 { return Err(io::Error::last_os_error()); } } Ok(()) } // The first time we unblock SIGTIMEOUT should cause approprate initialization: static SETUP_TIMEOUT_HANDLER: std::sync::Once = std::sync::Once::new(); /// Setup our timeout-signal workflow. This establishes the signal handler for /// our `SIGTIMEOUT` and should be called once during initialization. #[inline] pub fn setup_timeout_handler() { SETUP_TIMEOUT_HANDLER.call_once(|| { // We unwrap here. // If setting up this handler fails you have other problems already, // plus, if setting up fails you can't *use* it either, so everything // goes to die. do_setup_timeout_handler().unwrap(); }); } /// This guards the state of the timeout signal: We want it blocked usually. pub struct TimeoutBlockGuard(bool); impl Drop for TimeoutBlockGuard { fn drop(&mut self) { if self.0 { block_timeout_signal(); } else { unblock_timeout_signal().forget(); } } } impl TimeoutBlockGuard { /// Convenience helper to "forget" to restore the signal block mask. #[inline(always)] pub fn forget(self) { std::mem::forget(self); } /// Convenience helper to trigger the guard behavior immediately. #[inline(always)] pub fn trigger(self) { std::mem::drop(self); // be explicit here... } } /// Unblock the timeout signal for the current thread. By default we block the /// signal this behavior should be restored when done using timeouts, therefor this /// returns a guard: #[inline(always)] pub fn unblock_timeout_signal() -> TimeoutBlockGuard { // This calls std::sync::Once: setup_timeout_handler(); //let mut set = nix::sys::signal::SigSet::empty(); //set.add(SIGTIMEOUT.0); //set.thread_unblock()?; //Ok(TimeoutBlockGuard{}) // Again, nix crate and its signal limitations... // NOTE: // sigsetops(3) and pthread_sigmask(3) can only fail if invalid memory is // passed to the kernel, or signal numbers are "invalid", since we know // neither is the case we will panic on error... let was_blocked = unsafe { let mut mask = MaybeUninit::::uninit(); let mut oldset = MaybeUninit::::uninit(); if libc::sigemptyset(&mut *mask.as_mut_ptr()) != 0 || libc::sigaddset(&mut *mask.as_mut_ptr(), SIGTIMEOUT.0) != 0 || libc::pthread_sigmask( libc::SIG_UNBLOCK, &mask.assume_init(), &mut *oldset.as_mut_ptr(), ) != 0 { panic!("Impossibly failed to unblock SIGTIMEOUT"); //return Err(io::Error::last_os_error()); } libc::sigismember(&oldset.assume_init(), SIGTIMEOUT.0) == 1 }; TimeoutBlockGuard(was_blocked) } /// Block the timeout signal for the current thread. This is the default. #[inline(always)] pub fn block_timeout_signal() { //let mut set = nix::sys::signal::SigSet::empty(); //set.add(SIGTIMEOUT); //set.thread_block() unsafe { let mut mask = MaybeUninit::::uninit(); if libc::sigemptyset(&mut *mask.as_mut_ptr()) != 0 || libc::sigaddset(&mut *mask.as_mut_ptr(), SIGTIMEOUT.0) != 0 || libc::pthread_sigmask(libc::SIG_BLOCK, &mask.assume_init(), std::ptr::null_mut()) != 0 { panic!("Impossibly failed to block SIGTIMEOUT"); //return Err(io::Error::last_os_error()); } } }