use super::*;
contended!(
an_upgradable_read_upgrades_while_readers_and_a_writer_contend,
an_upgradable_read_upgrades_while_readers_and_a_writer_contend_at_bound_2,
10,
|| {
let lock = Arc::new(RwLock::new(UnsafeCell::new(0usize)));
let upgrader = {
let lock = Arc::clone(&lock);
thread::spawn(move || {
let upgradable = block_on(lock.upgradable_read());
let _ = read(&upgradable);
bump(&block_on(upgradable.upgrade()));
})
};
let reader = {
let lock = Arc::clone(&lock);
thread::spawn(move || {
let _ = read(&block_on(lock.read()));
})
};
bump(&block_on(lock.write()));
upgrader.join().expect("upgrader");
reader.join().expect("reader");
assert_eq!(read(&lock.try_write().expect("free")), 2);
},
|| {
let lock = Arc::new(RwLock::new(UnsafeCell::new(0usize)));
let upgradable = lock.try_upgradable_read().expect("free");
let reader = {
let lock = Arc::clone(&lock);
thread::spawn(move || {
let _ = lock.try_read().map(|shared| read(&shared));
})
};
let writer = {
let lock = Arc::clone(&lock);
thread::spawn(move || bump(&block_on(lock.write())))
};
bump(&block_on(upgradable.upgrade()));
reader.join().expect("reader");
writer.join().expect("writer");
assert_eq!(read(&lock.try_write().expect("free")), 2);
}
);
#[test]
fn a_dropped_upgrade_racing_the_last_reader_leaves_nothing_held() {
explore(
"a_dropped_upgrade_racing_the_last_reader_leaves_nothing_held",
3,
5,
|| {
let lock = Arc::new(RwLock::new(()));
let upgradable = lock.try_upgradable_read().expect("free");
let reader = {
let lock = Arc::clone(&lock);
thread::spawn(move || drop(lock.try_read()))
};
poll_once_then_drop(upgradable.upgrade());
reader.join().expect("reader");
assert!(lock.try_write().is_ok(), "nothing was left held");
},
);
}
#[test]
fn a_rwlock_writer_and_readers_exclude_each_other() {
explore(
"a_rwlock_writer_and_readers_exclude_each_other",
1,
10,
|| {
let lock = Arc::new(RwLock::new(UnsafeCell::new(0usize)));
let writer = {
let lock = Arc::clone(&lock);
thread::spawn(move || bump(&block_on(lock.write())))
};
let reader = {
let lock = Arc::clone(&lock);
thread::spawn(move || {
let _ = read(&block_on(lock.read()));
})
};
let _ = read(&block_on(lock.read()));
writer.join().expect("writer");
reader.join().expect("reader");
assert_eq!(read(&lock.try_write().expect("free")), 1);
},
);
}
contended!(
a_cancelled_rwlock_writer_racing_the_last_reader_loses_nothing,
a_cancelled_rwlock_writer_racing_the_last_reader_loses_nothing_at_bound_2,
10,
|| {
let lock = Arc::new(RwLock::new(UnsafeCell::new(0usize)));
let held = lock.try_read().expect("free");
let canceller = {
let lock = Arc::clone(&lock);
thread::spawn(move || poll_once_then_drop(lock.write()))
};
let writer = {
let lock = Arc::clone(&lock);
thread::spawn(move || bump(&block_on(lock.write())))
};
drop(held);
canceller.join().expect("canceller");
writer.join().expect("writer");
assert_eq!(read(&lock.try_write().expect("the lock was not leaked")), 1);
},
|| {
let lock = Arc::new(RwLock::new(UnsafeCell::new(0usize)));
let held = lock.try_read().expect("free");
let queued = Arc::new(AtomicBool::new(false));
let canceller = {
let (lock, flag) = (Arc::clone(&lock), Arc::clone(&queued));
thread::spawn(move || poll_once_then_drop(flag_when_queued(lock.write(), &flag)))
};
wait_for(&queued);
let writer = {
let lock = Arc::clone(&lock);
thread::spawn(move || bump(&block_on(lock.write())))
};
drop(held);
canceller.join().expect("canceller");
writer.join().expect("writer");
assert_eq!(read(&lock.try_write().expect("the lock was not leaked")), 1);
}
);
#[test]
fn a_dropped_upgrade_never_lets_a_writer_in_beside_the_next_upgradable_read() {
explore(
"a_dropped_upgrade_never_lets_a_writer_in_beside_the_next_upgradable_read",
3,
100,
|| {
let lock = Arc::new(RwLock::new(()));
let upgradable = lock.try_upgradable_read().expect("free");
let inside = Arc::new(AtomicBool::new(false));
let held = Arc::new(AtomicBool::new(false));
let second = {
let (lock, inside, held) =
(Arc::clone(&lock), Arc::clone(&inside), Arc::clone(&held));
thread::spawn(move || {
let shared = lock.try_read().expect("readers share with it");
inside.store(true, Ordering::Release);
thread::yield_now();
drop(shared);
if let Some(next) = lock.try_upgradable_read() {
held.store(true, Ordering::Release);
thread::yield_now();
held.store(false, Ordering::Release);
drop(next);
}
})
};
wait_for(&inside);
poll_once_then_drop(upgradable.upgrade());
if let Ok(alone) = lock.try_write() {
assert!(
!held.load(Ordering::Acquire),
"a writer got in while the upgradable read was held"
);
drop(alone);
}
second.join().expect("second");
assert!(lock.try_write().is_ok(), "nothing was left held");
},
);
}
#[test]
#[should_panic(expected = "a writer got in while the upgradable read was held")]
fn calibration_a_pass_that_takes_the_write_for_a_dropped_upgrade_lets_a_writer_in() {
const WRITER: usize = 1;
const UPGRADABLE: usize = 1 << 1;
const UPGRADING: usize = 1 << 2;
const ONE_READER: usize = 1 << 3;
explore(
"calibration_a_pass_that_takes_the_write_for_a_dropped_upgrade_lets_a_writer_in",
3,
1,
|| {
let state = Arc::new(AtomicUsize::new(UPGRADABLE | UPGRADING | ONE_READER));
let withdrawn = Arc::new(AtomicBool::new(false));
let held = Arc::new(AtomicBool::new(false));
let second = {
let (state, withdrawn, held) = (
Arc::clone(&state),
Arc::clone(&withdrawn),
Arc::clone(&held),
);
thread::spawn(move || {
state.fetch_sub(ONE_READER, Ordering::AcqRel);
let seen = state.load(Ordering::Acquire);
if seen & UPGRADING != 0
&& seen >> 3 == 0
&& state
.compare_exchange(
seen,
(seen & !(UPGRADABLE | UPGRADING)) | WRITER,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
&& withdrawn.load(Ordering::Acquire)
{
state.fetch_and(!WRITER, Ordering::AcqRel);
}
let free = state.load(Ordering::Acquire);
if free & (WRITER | UPGRADABLE) == 0
&& state
.compare_exchange(
free,
free | UPGRADABLE,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
held.store(true, Ordering::Release);
thread::yield_now();
held.store(false, Ordering::Release);
state.fetch_and(!UPGRADABLE, Ordering::AcqRel);
}
})
};
withdrawn.store(true, Ordering::Release);
state.fetch_and(!(UPGRADABLE | UPGRADING), Ordering::AcqRel);
if state
.compare_exchange(0, WRITER, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
assert!(
!held.load(Ordering::Acquire),
"a writer got in while the upgradable read was held"
);
state.fetch_and(!WRITER, Ordering::AcqRel);
}
second.join().expect("second");
},
);
}