use super::helpers::{pause_before_rewait, registered};
use crate::sync::{Condvar, Mutex};
use crate::{Clock, TestClock, Waiter};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, mpsc};
use std::thread;
use std::time::Duration;
fn hold(clock: &Clock, waiter: &Waiter) -> mpsc::SyncSender<()> {
let (checked, resume) = pause_before_rewait(clock);
waiter.signal.wake();
checked.recv().unwrap();
resume
}
fn parked(clock: &Clock, count: usize) -> bool {
clock
.paused
.as_ref()
.unwrap()
.state
.lock()
.unwrap()
.parked(count)
}
fn wait_then_resume(tester: &TestClock, count: usize, resume: mpsc::SyncSender<()>) -> bool {
let clock = tester.clock();
let paused = clock.paused.as_ref().unwrap();
let waited = Arc::new(AtomicBool::new(false));
*paused.before_parked_wait.lock().unwrap() = Some(Box::new({
let waited = waited.clone();
let resume = resume.clone();
move || {
waited.store(true, Ordering::SeqCst);
resume.send(()).unwrap();
}
}));
tester.wait_parked(count);
let waited = waited.load(Ordering::SeqCst);
if !waited {
paused.before_parked_wait.lock().unwrap().take();
resume.send(()).unwrap();
}
waited
}
#[test]
fn test_notification_keeps_registration_but_holds_parked_barrier() {
let mut tester = TestClock::new();
let clock = tester.clock();
let first = clock.now() + Duration::from_secs(5);
let later = clock.now() + Duration::from_secs(9);
let waiter = Arc::new(clock.waiter());
let waiting = thread::spawn({
let waiter = waiter.clone();
move || {
assert!(waiter.wait(Some(first)));
assert!(!waiter.wait(Some(later)));
}
});
tester.wait_registered(1);
let resume = hold(&clock, &waiter);
waiter.signal.notify_all();
assert_eq!(registered(&clock), 1);
assert_eq!(tester.next_deadline(), Some(first));
assert!(!parked(&clock, 1));
tester.wait_registered(1);
let waited = wait_then_resume(&tester, 1, resume);
let next = tester.next_deadline();
tester.advance_to(later);
waiting.join().unwrap();
assert!(waited);
assert_eq!(next, Some(later));
assert!(parked(&clock, 0));
}
#[test]
fn test_condvar_deadline_change_holds_parked_barrier() {
let mut tester = TestClock::new();
let clock = tester.clock();
let earlier = clock.now() + Duration::from_secs(1);
let first = clock.now() + Duration::from_secs(5);
let later = clock.now() + Duration::from_secs(9);
let pair = Arc::new((Mutex::new(first), Condvar::new(&clock)));
let worker = thread::spawn({
let pair = pair.clone();
move || {
let guard = pair.0.lock().unwrap();
let deadline = *guard;
let (guard, result) = pair.1.wait_deadline(guard, deadline).unwrap();
assert!(!result.timed_out());
let deadline = *guard;
let (_, result) = pair.1.wait_deadline(guard, deadline).unwrap();
result.timed_out()
}
});
tester.wait_registered(1);
let earlier_wait = thread::spawn({
let pair = pair.clone();
move || {
let (_, result) = pair
.1
.wait_deadline(pair.0.lock().unwrap(), earlier)
.unwrap();
result.timed_out()
}
});
tester.wait_registered(2);
let (checked, resume) = pause_before_rewait(&clock);
tester.advance_to(earlier);
checked.recv().unwrap();
assert!(earlier_wait.join().unwrap());
*pair.0.lock().unwrap() = later;
pair.1.notify_all();
assert_eq!(tester.next_deadline(), Some(first));
let waited = wait_then_resume(&tester, 1, resume);
let next = tester.next_deadline();
tester.advance_to(later);
assert!(worker.join().unwrap());
assert!(waited);
assert_eq!(next, Some(later));
}
#[test]
fn test_reached_registration_cannot_satisfy_parked_barrier() {
let mut tester = TestClock::new();
let clock = tester.clock();
let first = clock.now() + Duration::from_secs(1);
let later = clock.now() + Duration::from_secs(9);
let waiter = Arc::new(clock.waiter());
let waiting = thread::spawn({
let waiter = waiter.clone();
move || {
assert!(!waiter.wait(Some(first)));
assert!(!waiter.wait(Some(later)));
}
});
tester.wait_registered(1);
let resume = hold(&clock, &waiter);
tester.advance_to(first);
assert_eq!(registered(&clock), 1);
assert_eq!(tester.next_deadline(), Some(first));
assert!(!parked(&clock, 1));
tester.wait_registered(1);
let waited = wait_then_resume(&tester, 1, resume);
let next = tester.next_deadline();
tester.advance_to(later);
waiting.join().unwrap();
assert!(waited);
assert_eq!(next, Some(later));
}
#[test]
fn test_single_notification_leaves_unselected_wait_parked() {
let mut tester = TestClock::new();
let clock = tester.clock();
let deadline = clock.now() + Duration::from_secs(5);
let waiter = Arc::new(clock.waiter());
let first = thread::spawn({
let waiter = waiter.clone();
move || waiter.wait(Some(deadline))
});
tester.wait_registered(1);
let first_resume = hold(&clock, &waiter);
let second = thread::spawn({
let waiter = waiter.clone();
move || waiter.wait(Some(deadline))
});
tester.wait_registered(2);
let second_resume = hold(&clock, &waiter);
waiter.signal.notify_one();
assert_eq!(registered(&clock), 2);
assert_eq!(tester.next_deadline(), Some(deadline));
assert!(!parked(&clock, 1));
tester.wait_registered(2);
let waited = wait_then_resume(&tester, 1, first_resume);
assert!(first.join().unwrap());
assert_eq!(registered(&clock), 1);
assert_eq!(tester.next_deadline(), Some(deadline));
tester.advance_to(deadline);
second_resume.send(()).unwrap();
assert!(!second.join().unwrap());
assert!(waited);
}
#[test]
fn test_parked_barrier_waits_for_notifications_on_any_condvar() {
for broadcast in [false, true] {
let tester = TestClock::new();
let clock = tester.clock();
let stable = Arc::new(clock.waiter());
let changed = Arc::new(clock.waiter());
let stable_thread = thread::spawn({
let waiter = stable.clone();
move || waiter.wait(None)
});
let changed_thread = thread::spawn({
let waiter = changed.clone();
move || waiter.wait(None)
});
tester.wait_registered(2);
let resume = hold(&clock, &changed);
if broadcast {
changed.signal.notify_all();
} else {
changed.signal.notify_one();
}
tester.wait_registered(2);
let waited = wait_then_resume(&tester, 1, resume);
assert!(changed_thread.join().unwrap());
stable.signal.notify_all();
assert!(stable_thread.join().unwrap());
assert!(waited, "{broadcast}");
}
}