const EXPIRY_MESSAGE: &str = "Watchdog expired. Aborting.";
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
pub use posix_platform::Watchdog;
#[cfg(all(feature = "std", any(target_os = "windows", target_os = "macos")))]
pub use std_platform::Watchdog;
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
mod posix_platform {
use core::time::Duration;
use iceoryx2_pal_posix::posix::{
self, CLOCK_REALTIME, Errno, MemZeroedStruct, SIGALRM, SIGEV_SIGNAL, long, sigaction_t,
sighandler_t,
types::{int, itimerspec, sigevent, time_t, timer_t, timespec},
};
use super::EXPIRY_MESSAGE;
pub struct Watchdog {
timer_id: timer_t,
}
impl Drop for Watchdog {
fn drop(&mut self) {
let disarm = itimerspec {
it_interval: timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: timespec {
tv_sec: 0,
tv_nsec: 0,
},
};
unsafe {
posix::timer_settime(self.timer_id, 0, &disarm, core::ptr::null_mut());
posix::timer_delete(self.timer_id);
}
}
}
impl Default for Watchdog {
fn default() -> Self {
Self::new_with_timeout(Duration::from_secs(10))
}
}
unsafe extern "C" fn handler(_sig: int) {
signal_safe_write(EXPIRY_MESSAGE);
unsafe {
posix::abort();
}
}
impl Watchdog {
pub fn new_with_timeout(timeout: Duration) -> Self {
unsafe {
let mut sa = sigaction_t::new_zeroed();
sa.set_handler(handler as *const () as sighandler_t);
posix::sigaction(SIGALRM, &sa, &mut sigaction_t::new_zeroed());
let mut sev: sigevent = core::mem::zeroed();
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGALRM;
let mut timer_id: timer_t = core::mem::zeroed();
if posix::timer_create(CLOCK_REALTIME, &mut sev, &mut timer_id) == -1 {
panic!("failed to create POSIX timer, errno: {:?}", Errno::get());
}
let its = itimerspec {
it_interval: timespec {
tv_sec: 0,
tv_nsec: 0,
},
it_value: timespec {
tv_sec: timeout.as_secs() as time_t,
tv_nsec: timeout.subsec_nanos() as long,
},
};
if posix::timer_settime(timer_id, 0, &its, core::ptr::null_mut()) == -1 {
panic!("failed to arm POSIX timer, errno: {:?}", Errno::get());
}
Self { timer_id }
}
}
pub fn new() -> Self {
Self::default()
}
}
fn signal_safe_write(msg: &str) {
use core::fmt::Write as _;
let _ = iceoryx2_pal_print::writer::stderr().write_str(msg);
}
}
#[cfg(all(feature = "std", any(target_os = "windows", target_os = "macos")))]
mod std_platform {
use alloc::sync::Arc;
use core::time::Duration;
use std::{sync::Mutex, thread, time::Instant};
use super::EXPIRY_MESSAGE;
pub struct Watchdog {
termination_thread: Option<thread::JoinHandle<()>>,
keep_running: Arc<Mutex<bool>>,
}
impl Drop for Watchdog {
fn drop(&mut self) {
*self.keep_running.lock().unwrap() = false;
let handle = self.termination_thread.take();
handle.unwrap().join().unwrap();
}
}
impl Default for Watchdog {
fn default() -> Self {
Self::new_with_timeout(Duration::from_secs(10))
}
}
impl Watchdog {
pub fn new_with_timeout(timeout: Duration) -> Self {
let keep_running = Arc::new(Mutex::new(true));
Self {
keep_running: keep_running.clone(),
termination_thread: Some(thread::spawn(move || {
let now = Instant::now();
while *keep_running.lock().unwrap() {
std::thread::yield_now();
std::thread::sleep(Duration::from_millis(10));
std::thread::yield_now();
if now.elapsed() > timeout {
eprintln!("{EXPIRY_MESSAGE}");
std::process::exit(1);
}
}
})),
}
}
pub fn new() -> Self {
Self::default()
}
}
}