use super::std_timer_scheduler_state::StdTimerSchedulerState;
use super::std_timer_waiter::StdTimerWaiter;
use super::std_timer_worker_guard::StdTimerWorkerGuard;
use crate::internal::PanicFanout;
use crate::{
TimeError,
TimerUnavailableError,
};
use std::io;
#[cfg(coverage)]
use std::sync::atomic::{
AtomicBool,
AtomicUsize,
Ordering,
};
use std::sync::{
Arc,
Condvar,
Mutex,
MutexGuard,
OnceLock,
};
use std::time::Instant;
#[cfg(coverage)]
static FAIL_NEXT_WORKER_SPAWN: AtomicBool = AtomicBool::new(false);
#[cfg(coverage)]
static PANIC_NEXT_WORKER_RUN: AtomicBool = AtomicBool::new(false);
#[cfg(coverage)]
static WORKER_NOTIFICATION_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(coverage)]
pub fn reset_std_timer_worker_notification_count() {
WORKER_NOTIFICATION_COUNT.store(0, Ordering::Release);
}
#[cfg(coverage)]
#[must_use]
pub fn std_timer_worker_notification_count() -> usize {
WORKER_NOTIFICATION_COUNT.load(Ordering::Acquire)
}
#[cfg(coverage)]
pub fn fail_next_std_timer_worker_spawn() {
FAIL_NEXT_WORKER_SPAWN.store(true, Ordering::Release);
}
#[cfg(coverage)]
pub fn panic_next_std_timer_worker() {
let scheduler = StdTimerScheduler::shared();
let _state = scheduler.lock_state();
PANIC_NEXT_WORKER_RUN.store(true, Ordering::Release);
scheduler.notify_worker();
}
pub(crate) struct StdTimerScheduler {
state: Mutex<StdTimerSchedulerState>,
changed: Condvar,
}
impl StdTimerScheduler {
#[must_use]
#[inline]
pub(crate) fn new() -> Self {
Self {
state: Mutex::new(StdTimerSchedulerState::new()),
changed: Condvar::new(),
}
}
#[must_use]
#[inline]
pub(crate) fn shared() -> Arc<Self> {
static SCHEDULER: OnceLock<Arc<StdTimerScheduler>> = OnceLock::new();
Arc::clone(SCHEDULER.get_or_init(|| Arc::new(Self::new())))
}
pub(crate) fn register(
self: &Arc<Self>,
deadline: Instant,
waiter: Arc<StdTimerWaiter>,
) -> Result<u64, TimeError> {
let mut state = self.lock_state();
let previous_deadline = state.next_deadline();
let waiter_id = state.register(deadline, waiter);
let next_deadline_changed = state.next_deadline() != previous_deadline;
if state.worker_running() {
drop(state);
if next_deadline_changed {
self.notify_worker();
}
return Ok(waiter_id);
}
let worker_generation = state.mark_worker_started();
self.spawn_worker(state, waiter_id, worker_generation)
}
#[inline]
pub(crate) fn cancel(&self, waiter_id: u64) {
let mut state = self.lock_state();
let previous_deadline = state.next_deadline();
let waiter = state.cancel(waiter_id);
let next_deadline_changed =
waiter.is_some() && state.next_deadline() != previous_deadline;
drop(state);
drop(waiter);
if next_deadline_changed {
self.notify_worker();
}
}
pub(super) fn handle_worker_exit(&self, worker_generation: u64) {
let waiters = self
.lock_state()
.stop_worker_and_take_waiters(worker_generation);
let wakers =
waiters.iter().filter_map(|waiter| waiter.fail()).collect();
drop(waiters);
let mut fanout = PanicFanout::new();
fanout.wake_all(wakers);
fanout.discard_panics();
}
#[inline]
fn notify_worker(&self) {
#[cfg(coverage)]
WORKER_NOTIFICATION_COUNT.fetch_add(1, Ordering::AcqRel);
self.changed.notify_one();
}
fn spawn_worker(
self: &Arc<Self>,
mut state: MutexGuard<'_, StdTimerSchedulerState>,
waiter_id: u64,
worker_generation: u64,
) -> Result<u64, TimeError> {
let scheduler = Arc::clone(self);
let spawn_result =
Self::spawn_native_worker(scheduler, worker_generation);
if let Err(source) = spawn_result {
return Err(Self::rollback_failed_worker_start(
&mut state,
waiter_id,
worker_generation,
source,
));
}
drop(state);
Ok(waiter_id)
}
fn spawn_native_worker(
scheduler: Arc<Self>,
worker_generation: u64,
) -> io::Result<std::thread::JoinHandle<()>> {
#[cfg(coverage)]
if FAIL_NEXT_WORKER_SPAWN.swap(false, Ordering::AcqRel) {
return Err(io::Error::other(
"injected standard Timer worker spawn failure",
));
}
std::thread::Builder::new()
.name("qubit-clock-timer".to_owned())
.spawn(move || {
let startup_guard = StdTimerWorkerGuard::new(
scheduler.as_ref(),
worker_generation,
);
let _worker_guard = startup_guard.handoff();
scheduler.run();
})
}
fn rollback_failed_worker_start(
state: &mut StdTimerSchedulerState,
waiter_id: u64,
worker_generation: u64,
source: io::Error,
) -> TimeError {
drop(state.cancel(waiter_id));
state.mark_worker_stopped(worker_generation);
TimeError::TimerUnavailable {
source: TimerUnavailableError::WorkerThreadSpawnFailed { source },
}
}
fn run(&self) {
let mut state = self.lock_state();
loop {
#[cfg(coverage)]
if PANIC_NEXT_WORKER_RUN.swap(false, Ordering::AcqRel) {
panic!("injected standard Timer worker failure");
}
if state.is_empty() {
state = self
.changed
.wait(state)
.unwrap_or_else(std::sync::PoisonError::into_inner);
continue;
}
let deadline = state.next_deadline().expect(
"active standard Timer registration must have a deadline",
);
let now = Instant::now();
if deadline > now {
let duration = deadline.duration_since(now);
let (next_state, _) = self
.changed
.wait_timeout(state, duration)
.unwrap_or_else(std::sync::PoisonError::into_inner);
state = next_state;
continue;
}
let due_waiters = state.take_due(now);
drop(state);
let wakers = due_waiters
.iter()
.filter_map(|waiter| waiter.complete())
.collect();
drop(due_waiters);
let mut fanout = PanicFanout::new();
fanout.wake_all(wakers);
fanout.discard_panics();
state = self.lock_state();
}
}
#[inline(always)]
fn lock_state(&self) -> MutexGuard<'_, StdTimerSchedulerState> {
self.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}