Skip to content

Commit c16dbc3

Browse files
committed
add atomic downgrade test
1 parent fd53508 commit c16dbc3

File tree

1 file changed

+78
-12
lines changed

1 file changed

+78
-12
lines changed

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

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use rand::Rng;
2-
3-
use crate::hint::spin_loop;
41
use crate::sync::atomic::{AtomicUsize, Ordering};
52
use crate::sync::mpsc::channel;
63
use crate::sync::{
74
Arc, Barrier, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard,
85
RwLockWriteGuard, TryLockError,
96
};
107
use crate::thread;
8+
use crate::time;
9+
use rand::Rng;
1110

1211
#[derive(Eq, PartialEq, Debug)]
1312
struct NonCopy(i32);
@@ -592,12 +591,68 @@ fn test_downgrade_readers() {
592591
}
593592
}
594593

595-
#[test]
596594
fn test_downgrade_atomic() {
595+
// The number of evil writer threads.
596+
const W: usize = 1024;
597+
let rw = Arc::new(RwLock::new(0i32));
598+
599+
// Put the lock in write mode, making all future threads trying to access this go to sleep.
600+
let mut main_write_guard = rw.write().unwrap();
601+
602+
let mut handles = Vec::with_capacity(W);
603+
604+
for _ in 0..W {
605+
let w = rw.clone();
606+
let evil_handle = thread::spawn(move || {
607+
// Will go to sleep since the main thread initially has the write lock.
608+
let mut evil_guard = w.write().unwrap();
609+
*evil_guard += 1;
610+
});
611+
612+
handles.push(evil_handle);
613+
}
614+
615+
// Wait for a good amount of time so that evil threads go to sleep.
616+
// Note that this is not striclty necessary.
617+
let eternity = time::Duration::from_secs(1);
618+
thread::sleep(eternity);
619+
620+
// Once everyone is asleep, set the value to -1.
621+
*main_write_guard = -1;
622+
623+
// Atomically downgrade the write guard into a read guard.
624+
let main_read_guard = RwLockWriteGuard::downgrade(main_write_guard);
625+
626+
// If the above is not atomic, then it is possible for an evil thread to get in front of this
627+
// read and change the value to be non-negative.
628+
assert_eq!(*main_read_guard, -1, "`downgrade` was not atomic");
629+
630+
drop(main_read_guard);
631+
632+
for handle in handles {
633+
handle.join().unwrap();
634+
}
635+
636+
let final_check = rw.read().unwrap();
637+
assert_eq!(*final_check, W as i32 - 1);
638+
}
639+
640+
#[test]
641+
#[ignore]
642+
fn test_downgrade_atomic_many() {
643+
for _ in 0..10 {
644+
test_downgrade_atomic();
645+
}
646+
}
647+
648+
#[test]
649+
#[ignore]
650+
fn test_downgrade_wake_readers() {
651+
// The number of reader threads.
597652
const R: usize = 16;
598653

599654
let r = Arc::new(RwLock::new(0));
600-
// The number of reader threads that observe the correct value.
655+
// The number of reader threads that have observed the correct value.
601656
let observers = Arc::new(AtomicUsize::new(0));
602657

603658
let w = r.clone();
@@ -619,9 +674,13 @@ fn test_downgrade_atomic() {
619674
for _ in 0..R {
620675
let r = r.clone();
621676
let observers = observers.clone();
677+
678+
// Spawn a thread that wants to observe the value that the main thread writes (not evil).
622679
let handle = thread::spawn(move || {
623680
// Will go to sleep since the main thread initially has the write lock.
624681
let read_guard = r.read().unwrap();
682+
eprintln!("\tObserved {}", *read_guard);
683+
625684
if *read_guard == 1 {
626685
observers.fetch_add(1, Ordering::Relaxed);
627686
}
@@ -630,25 +689,32 @@ fn test_downgrade_atomic() {
630689
reader_handles.push(handle);
631690
}
632691

692+
// FIXME Come up with a better way to make sure everyone is sleeping.
693+
let eternity = time::Duration::from_secs(5);
694+
695+
// Make sure that everyone else is actually sleeping.
696+
thread::sleep(eternity);
697+
698+
// Spawn an evil thread that will try to write before the readers have read.
633699
let evil = r.clone();
634700
let evil_handle = thread::spawn(move || {
635701
// Will go to sleep since the main thread initially has the write lock.
636702
let mut evil_guard = evil.write().unwrap();
703+
eprintln!("Evil thread is writing now");
637704
*evil_guard = 2;
638705
});
639706

640-
// FIXME Come up with a better way to make sure everyone is sleeping.
641707
// Make sure that everyone else is actually sleeping.
642-
let spin = 1000000;
643-
for _ in 0..spin {
644-
spin_loop();
645-
}
708+
thread::sleep(eternity);
646709

647710
// Once everyone is asleep, set the value to 1.
648711
*main_write_guard = 1;
649712

713+
// As of now, the `R` reader threads and the 1 evil thread _should_ be sleeping.
714+
eprintln!("\n\nBeginning Test\n\n");
715+
650716
// Atomically downgrade the write guard into a read guard.
651-
// This should wake up all of the reader threads, and allow them to also take the read lock.
717+
// This should wake up some of the reader threads, and allow them to also take the read lock.
652718
let main_read_guard = RwLockWriteGuard::downgrade(main_write_guard);
653719

654720
// If the above is not atomic, then it is possible for the evil thread to get in front of the
@@ -668,5 +734,5 @@ fn test_downgrade_atomic() {
668734
let final_check = r.read().unwrap();
669735
assert_eq!(*final_check, 2);
670736

671-
assert!(observers.load(Ordering::Relaxed) > 0, "No readers observed the correct value");
737+
assert_ne!(observers.load(Ordering::Relaxed), 0, "No readers observed the correct value");
672738
}

0 commit comments

Comments
 (0)