Skip to content

Commit 9f04522

Browse files
committed
add atomic correctness test for rwlock downgrade
1 parent f06c64c commit 9f04522

File tree

1 file changed

+55
-1
lines changed

1 file changed

+55
-1
lines changed

library/std/src/sync/rwlock/tests.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::sync::{
66
Arc, Barrier, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard,
77
RwLockWriteGuard, TryLockError,
88
};
9-
use crate::thread;
9+
use crate::{thread, time};
1010

1111
#[derive(Eq, PartialEq, Debug)]
1212
struct NonCopy(i32);
@@ -542,6 +542,9 @@ fn test_downgrade_readers() {
542542
const R: usize = 16;
543543
const N: usize = 1000;
544544

545+
// Starts up 1 writing thread and `R` reader threads.
546+
// The writer thread will constantly update the value inside the `RwLock`, and this test will
547+
// only pass if every reader observes all values between 0 and `N`.
545548
let r = Arc::new(RwLock::new(0));
546549
let b = Arc::new(Barrier::new(R + 1));
547550

@@ -587,3 +590,54 @@ fn test_downgrade_readers() {
587590
});
588591
}
589592
}
593+
594+
#[test]
595+
fn test_downgrade_atomic() {
596+
// Spawns many evil writer threads that will try and write to the locked value before the
597+
// intial writer who has the exlusive lock can read after it downgrades.
598+
// If the `RwLock` behaves correctly, then the initial writer should read the value it wrote
599+
// itself as no other thread should get in front of it.
600+
601+
// The number of evil writer threads.
602+
const W: usize = if cfg!(miri) { 100 } else { 1000 };
603+
let rw = Arc::new(RwLock::new(0i32));
604+
605+
// Put the lock in write mode, making all future threads trying to access this go to sleep.
606+
let mut main_write_guard = rw.write().unwrap();
607+
608+
// Spawn all of the evil writer threads.
609+
let handles: Vec<_> = (0..W)
610+
.map(|_| {
611+
let w = rw.clone();
612+
thread::spawn(move || {
613+
// Will go to sleep since the main thread initially has the write lock.
614+
let mut evil_guard = w.write().unwrap();
615+
*evil_guard += 1;
616+
})
617+
})
618+
.collect();
619+
620+
// Wait for a good amount of time so that evil threads go to sleep.
621+
// (Note that this is not striclty necessary...)
622+
let eternity = time::Duration::from_secs(1);
623+
thread::sleep(eternity);
624+
625+
// Once everyone is asleep, set the value to -1.
626+
*main_write_guard = -1;
627+
628+
// Atomically downgrade the write guard into a read guard.
629+
let main_read_guard = RwLockWriteGuard::downgrade(main_write_guard);
630+
631+
// If the above is not atomic, then it is possible for an evil thread to get in front of this
632+
// read and change the value to be non-negative.
633+
assert_eq!(*main_read_guard, -1, "`downgrade` was not atomic");
634+
635+
// Clean up everything now
636+
drop(main_read_guard);
637+
for handle in handles {
638+
handle.join().unwrap();
639+
}
640+
641+
let final_check = rw.read().unwrap();
642+
assert_eq!(*final_check, W as i32 - 1);
643+
}

0 commit comments

Comments
 (0)