#![cfg_attr(test, allow(clippy::unwrap_used, reason = "test scope"))]
use std::{sync::Arc, thread, time::Duration};
use super::registry::TaskRegistry;
use super::state::TASK_STATE_BLOCK_SIZE;
use super::token::{SchedulerStateLease, TaskLifecycleToken};
#[derive(Debug, Clone, Copy)]
enum Retire {
Complete,
DropUnstarted,
Cancel,
}
impl Retire {
fn apply(self, lifecycle: TaskLifecycleToken<SchedulerStateLease>) {
match self {
Self::Complete => {
lifecycle.start(0).complete();
}
Self::DropUnstarted => drop(lifecycle),
Self::Cancel => {
assert!(lifecycle.start_unless_cancelled(0).is_none());
}
}
}
}
const SWEEP_ATTEMPTS: usize = if cfg!(miri) { 64 } else { 20_000 };
const ROUNDS: usize = if cfg!(miri) { 1 } else { 100 };
fn retire_last_slot_during_sweep(retire: Retire) {
let registry = Arc::new(TaskRegistry::new());
for _ in 1..TASK_STATE_BLOCK_SIZE - 1 {
let (_, lifecycle) = registry.register_next_task();
lifecycle.start(0).complete();
}
let (last_id, last) = unsafe { registry.register_next_scheduled_task() };
assert_eq!(last_id, (TASK_STATE_BLOCK_SIZE - 1) as u64);
if matches!(retire, Retire::Cancel) {
registry.request_cancel(last_id).unwrap();
}
let swept_while_racing = thread::scope(|scope| {
let retiring = scope.spawn(move || retire.apply(last));
let sweeping = scope.spawn(|| {
let mut retired = 0;
for _ in 0..SWEEP_ATTEMPTS {
retired += registry.cleanup_completed(Duration::ZERO);
if retired > 0 {
break;
}
thread::yield_now();
}
retired
});
retiring.join().unwrap();
sweeping.join().unwrap()
});
let swept_after = registry.cleanup_completed(Duration::ZERO);
assert_eq!(
swept_while_racing + swept_after,
1,
"the settled block retires exactly once, whichever sweep sees it"
);
assert_eq!(registry.blocks.read().unwrap().resident(), 0);
for id in 1..=last_id {
assert!(registry.is_completed(id), "id {id}");
}
}
#[test]
fn completing_the_last_scheduled_token_races_a_sweep_soundly() {
for _ in 0..ROUNDS {
retire_last_slot_during_sweep(Retire::Complete);
}
}
#[test]
fn dropping_the_last_scheduled_token_races_a_sweep_soundly() {
for _ in 0..ROUNDS {
retire_last_slot_during_sweep(Retire::DropUnstarted);
}
}
#[test]
fn cancelling_the_last_scheduled_token_races_a_sweep_soundly() {
for _ in 0..ROUNDS {
retire_last_slot_during_sweep(Retire::Cancel);
}
}