use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex, Weak};
use std::thread;
use std::time::{Duration, Instant};
use crate::{TimelineSource, TimerCore};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimerPoll {
Pending {
remaining: Duration,
},
Fired {
newly_fired: bool,
},
}
#[derive(Debug)]
pub struct Timer {
deadline: Instant,
core: TimerCore,
}
impl Timer {
pub fn after(delay: Duration) -> Self {
let deadline = Instant::now()
.checked_add(delay)
.expect("timer delay exceeds the Instant range");
Self::at(deadline)
}
pub fn at(deadline: Instant) -> Self {
Self {
deadline,
core: TimerCore::new(1),
}
}
pub fn deadline(&self) -> Instant {
self.deadline
}
pub fn has_fired(&self) -> bool {
self.core.fired()
}
pub fn poll(&mut self) -> TimerPoll {
self.poll_at(Instant::now())
}
pub fn poll_at(&mut self, now: Instant) -> TimerPoll {
if self.core.fired() {
return TimerPoll::Fired { newly_fired: false };
}
if now < self.deadline {
return TimerPoll::Pending {
remaining: self.deadline.duration_since(now),
};
}
let newly_fired = self.core.tick(1);
TimerPoll::Fired { newly_fired }
}
pub fn wait(&mut self) -> TimerPoll {
loop {
match self.poll() {
TimerPoll::Pending { remaining } => thread::sleep(remaining),
fired @ TimerPoll::Fired { .. } => return fired,
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimeoutOperation<T> {
Pending,
Completed(T),
Unavailable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimeoutOutcome<T> {
Completed(T),
TimedOut,
Cancelled,
Unavailable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeoutPoll<'a, T> {
Pending {
remaining: Duration,
},
Completed(&'a T),
TimedOut,
Cancelled,
Unavailable,
}
#[derive(Debug)]
pub struct Timeout<T> {
timer: Timer,
outcome: Option<TimeoutOutcome<T>>,
}
impl<T> Timeout<T> {
pub fn after(delay: Duration) -> Self {
Self::from_timer(Timer::after(delay))
}
pub fn at(deadline: Instant) -> Self {
Self::from_timer(Timer::at(deadline))
}
pub fn from_timer(timer: Timer) -> Self {
Self {
timer,
outcome: None,
}
}
pub fn deadline(&self) -> Instant {
self.timer.deadline()
}
pub fn is_terminal(&self) -> bool {
self.outcome.is_some()
}
pub fn outcome(&self) -> Option<&TimeoutOutcome<T>> {
self.outcome.as_ref()
}
pub fn poll<F, C>(&mut self, operation: F, cancelled: C) -> TimeoutPoll<'_, T>
where
F: FnOnce() -> TimeoutOperation<T>,
C: FnOnce() -> bool,
{
self.poll_at(Instant::now(), operation, cancelled)
}
pub fn poll_at<F, C>(&mut self, now: Instant, operation: F, cancelled: C) -> TimeoutPoll<'_, T>
where
F: FnOnce() -> TimeoutOperation<T>,
C: FnOnce() -> bool,
{
if self.outcome.is_some() {
return self
.terminal_poll()
.expect("terminal timeout must have an outcome");
}
if matches!(self.timer.poll_at(now), TimerPoll::Fired { .. }) {
self.outcome = Some(TimeoutOutcome::TimedOut);
return TimeoutPoll::TimedOut;
}
let operation = operation();
if let TimeoutOperation::Completed(value) = operation {
self.outcome = Some(TimeoutOutcome::Completed(value));
return self
.terminal_poll()
.expect("completed timeout must have an outcome");
}
if cancelled() {
self.outcome = Some(TimeoutOutcome::Cancelled);
return TimeoutPoll::Cancelled;
}
if matches!(operation, TimeoutOperation::Unavailable) {
self.outcome = Some(TimeoutOutcome::Unavailable);
return TimeoutPoll::Unavailable;
}
match self.timer.poll_at(now) {
TimerPoll::Pending { remaining } => TimeoutPoll::Pending { remaining },
TimerPoll::Fired { .. } => {
unreachable!("timer was pending earlier in the same deterministic poll")
}
}
}
pub fn wait_with<P, C, N, W>(
&mut self,
mut operation: P,
mut cancelled: C,
mut now: N,
mut wait: W,
) -> &TimeoutOutcome<T>
where
P: FnMut() -> TimeoutOperation<T>,
C: FnMut() -> bool,
N: FnMut() -> Instant,
W: FnMut(Duration),
{
while let TimeoutPoll::Pending { remaining } =
self.poll_at(now(), &mut operation, &mut cancelled)
{
wait(remaining);
}
self.outcome
.as_ref()
.expect("terminal timeout must have an outcome")
}
fn terminal_poll(&self) -> Option<TimeoutPoll<'_, T>> {
self.outcome.as_ref().map(|outcome| match outcome {
TimeoutOutcome::Completed(value) => TimeoutPoll::Completed(value),
TimeoutOutcome::TimedOut => TimeoutPoll::TimedOut,
TimeoutOutcome::Cancelled => TimeoutPoll::Cancelled,
TimeoutOutcome::Unavailable => TimeoutPoll::Unavailable,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RevisionCheck {
Pending,
Satisfied,
Unavailable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RevisionWaitOutcome {
Satisfied { revision: u64 },
TimedOut { revision: u64 },
Cancelled { revision: u64 },
Disposed { revision: u64 },
Unavailable { revision: u64 },
}
#[derive(Debug)]
struct RevisionBarrierState {
revision: u64,
generation: u64,
disposed: bool,
}
#[derive(Debug)]
struct RevisionBarrierInner {
state: Mutex<RevisionBarrierState>,
changed: Condvar,
}
#[derive(Debug, Clone)]
pub struct BarrierCancellation {
cancelled: Arc<AtomicBool>,
barrier: Weak<RevisionBarrierInner>,
}
impl BarrierCancellation {
pub fn cancel(&self) -> bool {
if self.cancelled.swap(true, Ordering::AcqRel) {
return false;
}
if let Some(barrier) = self.barrier.upgrade() {
let mut state = barrier
.state
.lock()
.expect("revision barrier mutex poisoned");
state.generation = state.generation.wrapping_add(1);
barrier.changed.notify_all();
}
true
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
}
#[derive(Debug, Clone)]
pub struct RevisionBarrier {
inner: Arc<RevisionBarrierInner>,
}
impl RevisionBarrier {
pub fn new(initial_revision: u64) -> Self {
Self {
inner: Arc::new(RevisionBarrierInner {
state: Mutex::new(RevisionBarrierState {
revision: initial_revision,
generation: 0,
disposed: false,
}),
changed: Condvar::new(),
}),
}
}
pub fn revision(&self) -> u64 {
self.inner
.state
.lock()
.expect("revision barrier mutex poisoned")
.revision
}
pub fn advance(&self, revision: u64) -> bool {
let mut state = self
.inner
.state
.lock()
.expect("revision barrier mutex poisoned");
if state.disposed || revision <= state.revision {
return false;
}
state.revision = revision;
state.generation = state.generation.wrapping_add(1);
self.inner.changed.notify_all();
true
}
pub fn notify(&self) {
let mut state = self
.inner
.state
.lock()
.expect("revision barrier mutex poisoned");
if state.disposed {
return;
}
state.generation = state.generation.wrapping_add(1);
self.inner.changed.notify_all();
}
pub fn cancellation(&self) -> BarrierCancellation {
BarrierCancellation {
cancelled: Arc::new(AtomicBool::new(false)),
barrier: Arc::downgrade(&self.inner),
}
}
pub fn dispose(&self) -> bool {
let mut state = self
.inner
.state
.lock()
.expect("revision barrier mutex poisoned");
if state.disposed {
return false;
}
state.disposed = true;
state.generation = state.generation.wrapping_add(1);
self.inner.changed.notify_all();
true
}
pub fn is_disposed(&self) -> bool {
self.inner
.state
.lock()
.expect("revision barrier mutex poisoned")
.disposed
}
pub fn wait_after<F>(
&self,
after_revision: u64,
mut check: F,
mut deadline: Option<&mut Timer>,
cancellation: Option<&BarrierCancellation>,
) -> RevisionWaitOutcome
where
F: FnMut(u64) -> RevisionCheck,
{
if cancellation
.is_some_and(|token| !Weak::ptr_eq(&token.barrier, &Arc::downgrade(&self.inner)))
{
return RevisionWaitOutcome::Unavailable {
revision: self.revision(),
};
}
let mut state = self
.inner
.state
.lock()
.expect("revision barrier mutex poisoned");
loop {
if state.disposed {
return RevisionWaitOutcome::Disposed {
revision: state.revision,
};
}
if cancellation.is_some_and(BarrierCancellation::is_cancelled) {
return RevisionWaitOutcome::Cancelled {
revision: state.revision,
};
}
if state.revision > after_revision {
let revision = state.revision;
let generation = state.generation;
drop(state);
let checked = check(revision);
state = self
.inner
.state
.lock()
.expect("revision barrier mutex poisoned");
if state.generation != generation {
continue;
}
match checked {
RevisionCheck::Satisfied => {
return RevisionWaitOutcome::Satisfied { revision };
}
RevisionCheck::Unavailable => {
return RevisionWaitOutcome::Unavailable { revision };
}
RevisionCheck::Pending => {}
}
}
match deadline.as_deref_mut().map(Timer::poll) {
Some(TimerPoll::Fired { .. }) => {
return RevisionWaitOutcome::TimedOut {
revision: state.revision,
};
}
Some(TimerPoll::Pending { remaining }) => {
let (next_state, _) = self
.inner
.changed
.wait_timeout(state, remaining)
.expect("revision barrier mutex poisoned");
state = next_state;
}
None => {
state = self
.inner
.changed
.wait(state)
.expect("revision barrier mutex poisoned");
}
}
}
}
}