use alloc::{
borrow::{Cow, ToOwned},
sync::{Arc, Weak},
vec::Vec,
};
use core::{
sync::atomic::{AtomicBool, AtomicU64, Ordering},
time::Duration,
};
use ax_lazyinit::LazyLock;
use ax_runtime::hal::time::monotonic_time;
use axpoll::{IoEvents, Pollable};
use axpoll_set::PollSet;
use event_listener::{Event, listener};
use syscalls::Errno;
use crate::{
StarryError, StarryResult,
file::{FileLike, IoDst, IoSrc},
sync::Mutex,
task::{
current_user_task,
future::{block_on, block_on_user, poll_io, timeout_at, timeout_at_wall},
},
time::ClockDeadline,
};
pub const CLOCK_REALTIME: u32 = 0;
pub const CLOCK_MONOTONIC: u32 = 1;
pub const CLOCK_BOOTTIME: u32 = 7;
pub const CLOCK_REALTIME_ALARM: u32 = 8;
pub const CLOCK_BOOTTIME_ALARM: u32 = 9;
pub const TFD_TIMER_ABSTIME: u32 = 1;
pub const TFD_TIMER_CANCEL_ON_SET: u32 = 2;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TimerfdSetMode {
Relative,
Absolute,
AbsoluteCancelOnSet,
}
impl TimerfdSetMode {
fn is_absolute(self) -> bool {
!matches!(self, Self::Relative)
}
fn cancel_on_set(self) -> bool {
matches!(self, Self::AbsoluteCancelOnSet)
}
}
#[derive(Default)]
struct State {
next_deadline: Option<ClockDeadline>,
expired: bool,
interval: Duration,
cancel_on_set: bool,
canceled: bool,
shutdown: bool,
}
impl State {
fn rearm_periodic(&mut self) -> u64 {
if !self.expired || self.interval.is_zero() {
return 0;
}
let deadline = self
.next_deadline
.expect("expired timer retains its deadline");
self.expired = false;
let Some(lag) = deadline.lag() else {
return 0;
};
let interval_ns = self.interval.as_nanos();
let remainder_ns = lag.as_nanos() % interval_ns;
let remainder = Duration::new(
(remainder_ns / 1_000_000_000) as u64,
(remainder_ns % 1_000_000_000) as u32,
);
self.next_deadline = Some(
deadline
.saturating_add(lag)
.saturating_add(self.interval - remainder),
);
(lag.as_nanos() / interval_ns).min(u64::MAX as u128 - 1) as u64
}
}
static TIMERFD_INSTANCES: LazyLock<Mutex<Vec<Weak<Timerfd>>>> =
LazyLock::new(|| Mutex::new(Vec::new()));
pub struct Timerfd {
clockid: u32,
state: Mutex<State>,
expire_count: AtomicU64,
poll_rx: PollSet,
non_blocking: AtomicBool,
arm_event: Arc<Event>,
}
impl Timerfd {
pub fn new(clockid: u32) -> StarryResult<Arc<Self>> {
match clockid {
CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_BOOTTIME | CLOCK_REALTIME_ALARM
| CLOCK_BOOTTIME_ALARM => {}
_ => return Err(StarryError::InvalidInput),
}
let this = Arc::new(Self {
clockid,
state: Mutex::new(State::default()),
expire_count: AtomicU64::new(0),
poll_rx: PollSet::new(),
non_blocking: AtomicBool::new(false),
arm_event: Arc::new(Event::new()),
});
TIMERFD_INSTANCES.lock().push(Arc::downgrade(&this));
let weak = Arc::downgrade(&this);
crate::task::spawn_kernel_thread_with_stack(
move || block_on(run_timer(weak)),
"timerfd".to_owned(),
crate::task::default_task_stack_size(),
);
Ok(this)
}
pub fn settime(
&self,
mode: TimerfdSetMode,
new_value: Duration,
new_interval: Duration,
) -> StarryResult<(Duration, Duration)> {
let mut state = self.state.lock();
state.rearm_periodic();
let old_interval = state.interval;
let old_remaining = state
.next_deadline
.map(ClockDeadline::remaining)
.unwrap_or(Duration::ZERO);
if new_value.is_zero() {
state.next_deadline = None;
state.interval = Duration::ZERO;
state.cancel_on_set = false;
} else {
let deadline = if mode.is_absolute() {
match self.clockid {
CLOCK_REALTIME | CLOCK_REALTIME_ALARM => ClockDeadline::Realtime(new_value),
_ => ClockDeadline::Monotonic(new_value),
}
} else {
ClockDeadline::Monotonic(monotonic_time().saturating_add(new_value))
};
state.next_deadline = Some(deadline);
state.interval = new_interval;
state.cancel_on_set = mode.cancel_on_set() && deadline.is_realtime();
}
state.canceled = false;
state.expired = false;
self.expire_count.store(0, Ordering::Release);
drop(state);
self.arm_event.notify(usize::MAX);
Ok((old_interval, old_remaining))
}
pub fn gettime(&self) -> (Duration, Duration) {
let mut state = self.state.lock();
let rearmed = state.expired && !state.interval.is_zero();
let extra = state.rearm_periodic();
self.expire_count.fetch_add(extra, Ordering::AcqRel);
let result = (
state.interval,
state
.next_deadline
.map(ClockDeadline::remaining)
.unwrap_or(Duration::ZERO),
);
drop(state);
if rearmed {
self.arm_event.notify(usize::MAX);
}
result
}
fn take_expirations(&self) -> StarryResult<u64> {
let mut state = self.state.lock();
if state.canceled {
state.canceled = false;
if state.expired {
state.next_deadline = None;
state.expired = false;
}
self.expire_count.store(0, Ordering::Release);
return Err(Errno::ECANCELED.into());
}
let rearmed = state.expired && !state.interval.is_zero();
let extra = state.rearm_periodic();
if state.expired {
state.next_deadline = None;
state.expired = false;
}
let count = self
.expire_count
.swap(0, Ordering::AcqRel)
.saturating_add(extra);
drop(state);
if rearmed {
self.arm_event.notify(usize::MAX);
}
if count == 0 {
Err(StarryError::WouldBlock)
} else {
Ok(count)
}
}
}
pub fn notify_realtime_clock_changed() {
let timerfds = {
let mut registry = TIMERFD_INSTANCES.lock();
let mut timerfds = Vec::with_capacity(registry.len());
registry.retain(|weak| {
let Some(timerfd) = weak.upgrade() else {
return false;
};
timerfds.push(timerfd);
true
});
timerfds
};
for timerfd in timerfds {
let mut state = timerfd.state.lock();
if state.cancel_on_set {
state.canceled = true;
timerfd.expire_count.store(1, Ordering::Release);
drop(state);
timerfd.arm_event.notify(usize::MAX);
unsafe { timerfd.poll_rx.wake(IoEvents::IN) };
}
}
}
impl Drop for Timerfd {
fn drop(&mut self) {
let mut state = self.state.lock();
state.shutdown = true;
drop(state);
self.arm_event.notify(usize::MAX);
let self_ptr = core::ptr::from_ref(self);
TIMERFD_INSTANCES
.lock()
.retain(|weak| weak.as_ptr() != self_ptr && weak.strong_count() != 0);
}
}
async fn run_timer(weak: alloc::sync::Weak<Timerfd>) {
loop {
let arm_event = {
let Some(tfd) = weak.upgrade() else {
return;
};
tfd.arm_event.clone()
};
listener!(arm_event => listener);
let (deadline, shutdown) = {
let Some(tfd) = weak.upgrade() else {
return;
};
let state = tfd.state.lock();
(
if state.expired {
None
} else {
state.next_deadline
},
state.shutdown,
)
};
if shutdown {
return;
}
match deadline {
None => {
listener.await;
}
Some(dl) => {
let fired_timer = match dl {
ClockDeadline::Monotonic(deadline) => {
timeout_at(Some(deadline), listener).await.is_err()
}
ClockDeadline::Realtime(deadline) => {
timeout_at_wall(Some(deadline), listener).await.is_err()
}
};
if !fired_timer {
continue;
}
let Some(tfd) = weak.upgrade() else {
return;
};
let mut state = tfd.state.lock();
if state.shutdown {
return;
}
if !state.expired && state.next_deadline == Some(dl) {
state.expired = true;
tfd.expire_count.fetch_add(1, Ordering::AcqRel);
drop(state);
unsafe { tfd.poll_rx.wake(IoEvents::IN) };
}
}
}
}
}
impl FileLike for Timerfd {
fn read(&self, dst: &mut IoDst) -> StarryResult<usize> {
if dst.remaining_mut() < core::mem::size_of::<u64>() {
return Err(StarryError::InvalidInput);
}
let task = current_user_task();
block_on_user(
&task,
poll_io(self, IoEvents::IN, self.nonblocking(), || {
let n = self.take_expirations()?;
if let Err(e) = dst.write(&n.to_ne_bytes()) {
self.expire_count.fetch_add(n, Ordering::AcqRel);
unsafe { self.poll_rx.wake(IoEvents::IN) };
return Err(e.into());
}
Ok(core::mem::size_of::<u64>())
}),
)
.into_result()?
}
fn write(&self, _src: &mut IoSrc) -> StarryResult<usize> {
Err(StarryError::InvalidInput)
}
fn nonblocking(&self) -> bool {
self.non_blocking.load(Ordering::Acquire)
}
fn set_nonblocking(&self, non_blocking: bool) -> StarryResult {
self.non_blocking.store(non_blocking, Ordering::Release);
Ok(())
}
fn path(&self) -> Cow<'_, str> {
"anon_inode:[timerfd]".into()
}
}
impl Pollable for Timerfd {
fn poll(&self) -> IoEvents {
let mut events = IoEvents::empty();
events.set(IoEvents::IN, self.expire_count.load(Ordering::Acquire) > 0);
events
}
unsafe fn register_shared(
&self,
sink: &mut dyn axpoll::SharedRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
unsafe { sink.register_shared(&self.poll_rx, IoEvents::IN) };
}
}
unsafe fn register_exclusive(
&self,
sink: &mut dyn axpoll::ExclusiveRegistrationSink,
events: IoEvents,
) {
if events.contains(IoEvents::IN) {
unsafe { sink.register_exclusive(&self.poll_rx, IoEvents::IN) };
}
}
}
#[cfg(all(test, axtest))]
mod tests {
use super::*;
fn unspawned_timerfd() -> Arc<Timerfd> {
Arc::new(Timerfd {
clockid: CLOCK_REALTIME,
state: Mutex::new(State::default()),
expire_count: AtomicU64::new(0),
poll_rx: PollSet::new(),
non_blocking: AtomicBool::new(false),
arm_event: Arc::new(Event::new()),
})
}
#[axtest::axtest]
fn dropping_timerfd_unregisters_clock_change_observer() {
let timerfd = unspawned_timerfd();
let timerfd_ptr = Arc::as_ptr(&timerfd);
TIMERFD_INSTANCES.lock().push(Arc::downgrade(&timerfd));
drop(timerfd);
assert!(
!TIMERFD_INSTANCES
.lock()
.iter()
.any(|weak| weak.as_ptr() == timerfd_ptr),
"closed timerfd remained in the realtime clock observer registry"
);
}
}