use std::sync::Arc;
use std::sync::Barrier;
use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::task::Context;
use std::task::Poll;
use std::task::Wake;
use std::task::Waker;
use std::thread::Thread;
use std::thread::ThreadId;
use std::time::Duration;
use qubit_clock::StdMonotonicClock;
use qubit_clock::StdTimer;
use qubit_clock::Timer;
use qubit_clock::TimerFuture;
const CONCURRENT_WORKER_COUNT: usize = 16;
const REGISTRATIONS_PER_WORKER: usize = 64;
struct WakeThreadRecorder {
polling_thread: Thread,
notified: AtomicBool,
wake_thread: Mutex<Option<ThreadId>>,
}
impl WakeThreadRecorder {
#[must_use]
#[inline]
fn new(polling_thread: Thread) -> Self {
Self {
polling_thread,
notified: AtomicBool::new(false),
wake_thread: Mutex::new(None),
}
}
#[inline(always)]
fn prepare_to_poll(&self) {
self.notified.store(false, Ordering::Release);
}
fn park_until_notified(&self) {
while !self.notified.swap(false, Ordering::AcqRel) {
std::thread::park();
}
}
#[must_use]
fn wake_thread(&self) -> Option<ThreadId> {
*self
.wake_thread
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl Wake for WakeThreadRecorder {
#[inline(always)]
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
#[inline(always)]
fn wake_by_ref(self: &Arc<Self>) {
*self
.wake_thread
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(std::thread::current().id());
self.notified.store(true, Ordering::Release);
self.polling_thread.unpark();
}
}
fn block_on_with_wake_thread(mut future: TimerFuture) -> Option<ThreadId> {
let recorder = Arc::new(WakeThreadRecorder::new(std::thread::current()));
let waker = Waker::from(Arc::clone(&recorder));
let mut context = Context::from_waker(&waker);
loop {
recorder.prepare_to_poll();
if let Poll::Ready(result) = future.as_mut().poll(&mut context) {
result.expect("standard timer should complete");
return recorder.wake_thread();
}
recorder.park_until_notified();
}
}
fn observe_worker(timer: &dyn Timer) -> ThreadId {
for _ in 0..4 {
let future = timer
.after(Duration::from_millis(50))
.expect("worker observation deadline should register");
if let Some(worker) = block_on_with_wake_thread(future) {
return worker;
}
}
panic!("standard Timer worker should be observable within four attempts");
}
#[test]
fn test_block_on_with_wake_thread_accepts_immediate_ready_future() {
let future: TimerFuture = Box::pin(std::future::ready(Ok(())));
assert_eq!(block_on_with_wake_thread(future), None);
}
#[test]
fn test_std_timer_scheduler_shares_and_retains_process_worker() {
let first_clock = StdMonotonicClock::new();
let second_clock = StdMonotonicClock::new();
let first_timer = Arc::new(StdTimer::from_clock(&first_clock));
let second_timer = Arc::new(StdTimer::from_clock(&second_clock));
let first_waiter = Arc::clone(&first_timer);
let second_waiter = Arc::clone(&second_timer);
let first = std::thread::spawn(move || observe_worker(first_waiter.as_ref()));
let second = std::thread::spawn(move || observe_worker(second_waiter.as_ref()));
let first_worker = first.join().expect("first waiter should finish");
let second_worker = second.join().expect("second waiter should finish");
assert_eq!(first_worker, second_worker);
let retained_worker = observe_worker(first_timer.as_ref());
assert_eq!(first_worker, retained_worker);
}
#[test]
fn test_std_timer_scheduler_handles_parallel_lock_style_cancellation_churn() {
let clock = StdMonotonicClock::new();
let timer = Arc::new(StdTimer::from_clock(&clock));
let start = Arc::new(Barrier::new(CONCURRENT_WORKER_COUNT));
let registered = Arc::new(Barrier::new(CONCURRENT_WORKER_COUNT));
std::thread::scope(|scope| {
let mut workers = Vec::with_capacity(CONCURRENT_WORKER_COUNT);
for _ in 0..CONCURRENT_WORKER_COUNT {
let timer = Arc::clone(&timer);
let start = Arc::clone(&start);
let registered = Arc::clone(®istered);
workers.push(scope.spawn(move || {
start.wait();
let mut futures = Vec::with_capacity(REGISTRATIONS_PER_WORKER);
for _ in 0..REGISTRATIONS_PER_WORKER {
futures.push(
timer
.after(Duration::from_secs(60))
.expect("lock-style deadline should register"),
);
}
registered.wait();
drop(futures);
}));
}
for worker in workers {
worker.join().expect("cancellation worker should finish");
}
});
let survivor = timer
.after(Duration::from_millis(5))
.expect("post-cancellation deadline should register");
block_on_with_wake_thread(survivor);
}
#[test]
fn test_std_timer_scheduler_uses_one_worker_for_concurrent_deadlines() {
let clock = StdMonotonicClock::new();
let timer = Arc::new(StdTimer::from_clock(&clock));
let start = Arc::new(Barrier::new(CONCURRENT_WORKER_COUNT));
let worker_threads = std::thread::scope(|scope| {
let mut waiters = Vec::with_capacity(CONCURRENT_WORKER_COUNT);
for _ in 0..CONCURRENT_WORKER_COUNT {
let timer = Arc::clone(&timer);
let start = Arc::clone(&start);
waiters.push(scope.spawn(move || {
start.wait();
observe_worker(timer.as_ref())
}));
}
waiters
.into_iter()
.map(|waiter| waiter.join().expect("deadline waiter should finish"))
.collect::<Vec<_>>()
});
let first_worker = worker_threads
.first()
.expect("at least one worker thread should be observed");
assert!(
worker_threads.iter().all(|worker| worker == first_worker),
"all concurrent deadlines should use one scheduler worker",
);
}