use std::io;
use std::sync::Arc;
use std::sync::Condvar;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::OnceLock;
#[cfg(coverage)]
use std::sync::atomic::AtomicBool;
#[cfg(coverage)]
use std::sync::atomic::AtomicUsize;
#[cfg(coverage)]
use std::sync::atomic::Ordering;
use std::time::Instant;
use super::std_timer_scheduler_state::StdTimerSchedulerState;
use super::std_timer_waiter::StdTimerWaiter;
use super::std_timer_worker_guard::StdTimerWorkerGuard;
use crate::TimeError;
use crate::TimerUnavailableError;
use crate::internal::PanicFanout;
#[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)
}
}