@@ -6,7 +6,7 @@ use crate::sync::{
6
6
Arc , Barrier , MappedRwLockReadGuard , MappedRwLockWriteGuard , RwLock , RwLockReadGuard ,
7
7
RwLockWriteGuard , TryLockError ,
8
8
} ;
9
- use crate :: thread;
9
+ use crate :: { thread, time } ;
10
10
11
11
#[ derive( Eq , PartialEq , Debug ) ]
12
12
struct NonCopy ( i32 ) ;
@@ -542,6 +542,9 @@ fn test_downgrade_readers() {
542
542
const R : usize = 16 ;
543
543
const N : usize = 1000 ;
544
544
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`.
545
548
let r = Arc :: new ( RwLock :: new ( 0 ) ) ;
546
549
let b = Arc :: new ( Barrier :: new ( R + 1 ) ) ;
547
550
@@ -587,3 +590,54 @@ fn test_downgrade_readers() {
587
590
} ) ;
588
591
}
589
592
}
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