use std::future::poll_fn;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::task::Waker;
use loom::thread;
use super::mpsc::{block_on, explore};
use crate::sync::contend;
use crate::sync::fair_semaphore::{FairSemaphore, FairWait};
use crate::sync::queue::{Policy, QUEUED};
use crate::sync::raw_semaphore::{PermitWait, RawSemaphore};
fn lock_refusal(give_back: bool, until_queued: bool, witnessed: Arc<AtomicUsize>) {
let sem = Arc::new(RawSemaphore::new(1));
let waited = Arc::new(AtomicBool::new(false));
let waiter = {
let (sem, waited) = (Arc::clone(&sem), Arc::clone(&waited));
thread::spawn(move || {
let mut wait = PermitWait::new();
block_on(poll_fn(|cx| {
let poll = wait.poll_one(&sem, cx.waker());
if poll.is_pending() {
waited.store(true, Ordering::SeqCst);
}
poll
}));
sem.release(1);
})
};
if sem.take_one().is_ok() {
if until_queued {
for _ in 0..4 {
let state = sem
.queue()
.state
.load(crate::sync::internal::Ordering::Acquire);
if state & QUEUED != 0 {
break;
}
thread::yield_now();
}
}
if waited.load(Ordering::SeqCst) {
witnessed.fetch_add(1, Ordering::SeqCst);
}
if give_back {
assert!(contend::admitted(&*sem, 1, false).is_pending());
}
}
waiter.join().expect("waiter");
assert_eq!(sem.available(), 1, "a permit was lost or made up");
}
#[test]
fn a_lock_refused_for_the_budget_hands_itself_to_a_queued_waiter() {
let witnessed = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&witnessed);
explore(
"a_lock_refused_for_the_budget_hands_itself_to_a_queued_waiter",
3,
20,
move || lock_refusal(true, true, Arc::clone(&seen)),
);
assert!(
witnessed.load(Ordering::SeqCst) > 0,
"the search never refused a lock while the waiter was queued"
);
}
#[test]
fn a_lock_refused_for_the_budget_anywhere_in_the_waiters_attempt_loses_nothing() {
explore(
"a_lock_refused_for_the_budget_anywhere_in_the_waiters_attempt_loses_nothing",
3,
20,
|| lock_refusal(true, false, Arc::new(AtomicUsize::new(0))),
);
}
#[test]
#[should_panic(expected = "deadlock")]
fn calibration_a_refused_lock_that_keeps_the_permit_strands_the_waiter() {
explore(
"calibration_a_refused_lock_that_keeps_the_permit_strands_the_waiter",
3,
1,
|| lock_refusal(false, true, Arc::new(AtomicUsize::new(0))),
);
}
#[test]
fn a_send_refused_for_the_budget_hands_its_slot_to_a_queued_send() {
let witnessed = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&witnessed);
explore(
"a_send_refused_for_the_budget_hands_its_slot_to_a_queued_send",
3,
20,
move || {
let sem = Arc::new(FairSemaphore::new(1));
let waited = Arc::new(AtomicBool::new(false));
let sender = {
let (sem, waited) = (Arc::clone(&sem), Arc::clone(&waited));
thread::spawn(move || {
let mut wait = FairWait::new();
let taken = block_on(poll_fn(|cx| {
let poll = wait.poll(&sem, 1, cx.waker(), |n| sem.release(n));
if poll.is_pending() {
waited.store(true, Ordering::SeqCst);
}
poll
}));
assert!(taken.is_ok(), "the semaphore never closes here");
sem.release(1);
})
};
let mut wait = FairWait::new();
let refused = wait.poll_admitted(
&sem,
1,
Waker::noop(),
|| {
thread::yield_now();
if waited.load(Ordering::SeqCst) {
seen.fetch_add(1, Ordering::SeqCst);
}
false
},
|n| sem.release(n),
);
assert!(refused.is_pending(), "a completion past the spent budget");
wait.cancel(&sem, 1);
sender.join().expect("sender");
assert_eq!(sem.available(), 1, "a slot was lost or made up");
},
);
assert!(
witnessed.load(Ordering::SeqCst) > 0,
"the search never refused a send while the other was queued"
);
}