@@ -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 ) ]
1212struct 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