use crate::sync::{Condvar, Mutex};
use crate::{Clock, TestClock};
use loom::thread;
use std::sync::Arc;
use std::time::{Duration, UNIX_EPOCH};
fn assert_idle(tester: &TestClock, clock: &Clock) {
assert_eq!(tester.next_deadline(), None);
let state = clock.paused.as_ref().unwrap().state.lock().unwrap();
assert_eq!(state.blocked, 0);
assert!(state.signals.is_empty());
}
fn notification_model(holding: bool) {
loom::model(move || {
let tester = TestClock::new();
let clock = tester.clock();
let pair = Arc::new((Mutex::new(false), Condvar::new(&clock)));
let waiting = thread::spawn({
let pair = pair.clone();
move || {
let ready = pair.1.wait_while(pair.0.lock().unwrap(), |ready| !*ready);
assert!(*ready.unwrap());
}
});
let mut ready = pair.0.lock().unwrap();
*ready = true;
if holding {
pair.1.notify_one();
drop(ready);
} else {
drop(ready);
pair.1.notify_one();
}
waiting.join().unwrap();
drop(pair);
assert_idle(&tester, &clock);
});
}
#[test]
fn test_wait_observes_notification_under_mutex() {
notification_model(true);
}
#[test]
fn test_wait_observes_notification_after_unlock() {
notification_model(false);
}
fn two_waiters_model(broadcast: bool) {
loom::model(move || {
let tester = TestClock::new();
let clock = tester.clock();
let pair = Arc::new((Mutex::new(false), Condvar::new(&clock)));
let waiters: Vec<_> = (0..2)
.map(|_| {
let pair = pair.clone();
thread::spawn(move || {
let ready = pair.1.wait_while(pair.0.lock().unwrap(), |ready| !*ready);
assert!(*ready.unwrap());
})
})
.collect();
tester.wait_blocked(2);
*pair.0.lock().unwrap() = true;
if broadcast {
pair.1.notify_all();
} else {
pair.1.notify_one();
pair.1.notify_one();
}
for waiter in waiters {
waiter.join().unwrap();
}
drop(pair);
assert_idle(&tester, &clock);
});
}
#[test]
fn test_two_notify_one_calls_release_two_waiters() {
two_waiters_model(false);
}
#[test]
fn test_notify_all_releases_two_waiters() {
two_waiters_model(true);
}
#[test]
fn test_wait_deadline_observes_exact_advance() {
loom::model(|| {
let mut tester = TestClock::new();
tester.set_system_time(UNIX_EPOCH);
let clock = tester.clock();
let deadline = clock.now() + Duration::from_secs(1);
let waiting = thread::spawn({
let clock = clock.clone();
move || {
let condvar = Condvar::new(&clock);
let mutex = Mutex::new(());
let (_guard, result) = condvar
.wait_deadline(mutex.lock().unwrap(), deadline)
.unwrap();
assert!(result.timed_out());
assert_eq!(clock.now(), deadline);
assert_eq!(clock.system_time(), UNIX_EPOCH + Duration::from_secs(1));
}
});
tester.advance_to(deadline);
waiting.join().unwrap();
assert_idle(&tester, &clock);
});
}
#[test]
fn test_wait_deadline_observes_notification_after_short_advance() {
loom::model(|| {
let mut tester = TestClock::new();
let clock = tester.clock();
let start = clock.now();
let deadline = start + Duration::from_secs(2);
let pair = Arc::new((Mutex::new(false), Condvar::new(&clock)));
let waiting = thread::spawn({
let pair = pair.clone();
move || {
let (ready, result) = pair
.1
.wait_deadline(pair.0.lock().unwrap(), deadline)
.unwrap();
assert!(!result.timed_out());
assert!(*ready);
}
});
tester.wait_blocked(1);
tester.advance(Duration::from_secs(1));
*pair.0.lock().unwrap() = true;
pair.1.notify_one();
waiting.join().unwrap();
assert_eq!(clock.now(), start + Duration::from_secs(1));
drop(pair);
assert_idle(&tester, &clock);
});
}
#[test]
fn test_sleep_until_observes_exact_advance() {
loom::model(|| {
let mut tester = TestClock::new();
let clock = tester.clock();
let deadline = clock.now() + Duration::from_secs(1);
let sleeper = thread::spawn({
let clock = clock.clone();
move || {
clock.sleep_until(deadline);
assert_eq!(clock.now(), deadline);
}
});
tester.advance_to(deadline);
sleeper.join().unwrap();
assert_idle(&tester, &clock);
});
}
#[test]
fn test_wait_blocked_exposes_sleep_deadline() {
loom::model(|| {
let mut tester = TestClock::new();
let clock = tester.clock();
let deadline = clock.now() + Duration::from_secs(1);
let sleeper = thread::spawn({
let clock = clock.clone();
move || {
clock.sleep_until(deadline);
assert_eq!(clock.now(), deadline);
}
});
tester.wait_blocked(1);
assert_eq!(tester.next_deadline(), Some(deadline));
tester.advance_to(deadline);
sleeper.join().unwrap();
assert_idle(&tester, &clock);
});
}
#[test]
fn test_condvar_drop_removes_registration_during_advance() {
loom::model(|| {
let mut tester = TestClock::new();
let clock = tester.clock();
let creating = thread::spawn({
let clock = clock.clone();
move || drop(Condvar::new(&clock))
});
tester.advance(Duration::from_secs(1));
creating.join().unwrap();
assert_idle(&tester, &clock);
});
}
#[test]
fn test_notify_all_races_advance_with_mixed_waiters() {
loom::model(|| {
let mut tester = TestClock::new();
let clock = tester.clock();
let deadline = clock.now() + Duration::from_secs(1);
let pair = Arc::new((Mutex::new((false, false)), Condvar::new(&clock)));
let timed = thread::spawn({
let (pair, clock) = (pair.clone(), clock.clone());
move || {
let mut ready = pair.0.lock().unwrap();
loop {
let (next, result) = pair.1.wait_deadline(ready, deadline).unwrap();
ready = next;
if result.timed_out() {
assert_eq!(clock.now(), deadline);
break;
}
if ready.1 {
break;
}
}
}
});
let untimed = thread::spawn({
let pair = pair.clone();
move || {
let mut ready = pair
.1
.wait_while(pair.0.lock().unwrap(), |ready| !ready.0)
.unwrap();
assert!(ready.0);
ready.1 = true;
drop(ready);
pair.1.notify_all();
}
});
tester.wait_blocked(2);
pair.0.lock().unwrap().0 = true;
pair.1.notify_all();
tester.advance_to(deadline);
timed.join().unwrap();
untimed.join().unwrap();
drop(pair);
assert_idle(&tester, &clock);
});
}