use std::{
fmt,
fmt::Debug,
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
},
};
#[cfg(reifydb_dst)]
pub(crate) mod dst;
#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
pub mod scheduler;
#[cfg(reifydb_target = "wasi")]
pub(crate) mod wasi;
#[cfg(reifydb_target = "wasm")]
pub(crate) mod wasm;
#[cfg(reifydb_target = "wasi")]
use wasi::drain_expired_timers as wasi_drain;
use super::mailbox::SendError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Repeat {
Keep,
Cancel,
}
impl Repeat {
pub fn after_send<M>(result: Result<(), SendError<M>>) -> Self {
match result {
Ok(()) => Self::Keep,
Err(SendError::Full(_)) => Self::Keep,
Err(SendError::Closed(_)) => Self::Cancel,
}
}
pub fn is_keep(&self) -> bool {
matches!(self, Self::Keep)
}
}
#[derive(Clone)]
pub struct TimerHandle {
id: u64,
cancelled: Arc<AtomicBool>,
}
impl TimerHandle {
pub(crate) fn new(id: u64) -> Self {
Self {
id,
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) -> bool {
self.cancelled.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok()
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
pub fn id(&self) -> u64 {
self.id
}
pub(crate) fn cancelled_flag(&self) -> Arc<AtomicBool> {
self.cancelled.clone()
}
}
impl Debug for TimerHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TimerHandle").field("id", &self.id).field("cancelled", &self.is_cancelled()).finish()
}
}
static TIMER_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
pub(crate) fn next_timer_id() -> u64 {
TIMER_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[cfg(reifydb_target = "wasi")]
pub fn drain_expired_timers() {
wasi_drain();
}
#[cfg(not(reifydb_target = "wasi"))]
pub fn drain_expired_timers() {}
#[cfg(all(test, reifydb_target = "host", not(reifydb_dst)))]
mod tests {
use super::*;
use crate::actor::mailbox::create_mailbox;
#[test]
fn a_full_mailbox_drops_the_tick_but_keeps_the_timer_armed() {
let (actor, _mailbox) = create_mailbox::<u8>(Some(1));
assert_eq!(Repeat::after_send(actor.send(1)), Repeat::Keep, "precondition: the first send fits");
let full = actor.send(2);
assert!(
matches!(full, Err(SendError::Full(_))),
"precondition: capacity 1 must reject the second send"
);
assert_eq!(Repeat::after_send(full), Repeat::Keep, "backpressure must not retire a repeating timer");
}
#[test]
fn a_closed_mailbox_retires_the_timer() {
let (actor, mailbox) = create_mailbox::<u8>(Some(1));
drop(mailbox);
let closed = actor.send(1);
assert!(
matches!(closed, Err(SendError::Closed(_))),
"precondition: a dropped mailbox closes the channel"
);
assert_eq!(Repeat::after_send(closed), Repeat::Cancel, "a dead actor must retire its timer");
}
}