Skip to content

Commit 5d68316

Browse files
committed
fix memory ordering bug + bad test
This commit fixes a memory ordering bug in the futex implementation (`Relaxed` -> `Release` on `downgrade`). This commit also removes a badly written test that deadlocked and replaces it with a more reasonable test based on an already-tested `downgrade` test from the parking-lot crate.
1 parent 0604b8f commit 5d68316

File tree

2 files changed

+33
-48
lines changed

2 files changed

+33
-48
lines changed

std/src/sync/rwlock/tests.rs

+32-47
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use rand::Rng;
33
use crate::sync::atomic::{AtomicUsize, Ordering};
44
use crate::sync::mpsc::channel;
55
use crate::sync::{
6-
Arc, Barrier, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard,
7-
RwLockWriteGuard, TryLockError,
6+
Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
7+
TryLockError,
88
};
99
use crate::thread;
1010

@@ -511,57 +511,42 @@ fn test_downgrade_basic() {
511511
}
512512

513513
#[test]
514-
fn test_downgrade_readers() {
515-
// This test creates 1 writing thread and `R` reader threads doing `N` iterations.
516-
const R: usize = 10;
517-
const N: usize = if cfg!(target_pointer_width = "64") { 100 } else { 20 };
514+
fn test_downgrade_observe() {
515+
// Taken from the test `test_rwlock_downgrade` from:
516+
// https://github.com/Amanieu/parking_lot/blob/master/src/rwlock.rs
518517

519-
// The writer thread will constantly update the value inside the `RwLock`, and this test will
520-
// only pass if every reader observes all values between 0 and `N`.
521-
let rwlock = Arc::new(RwLock::new(0));
522-
let barrier = Arc::new(Barrier::new(R + 1));
523-
524-
// Create the writing thread.
525-
let r_writer = rwlock.clone();
526-
let b_writer = barrier.clone();
527-
thread::spawn(move || {
528-
for i in 0..N {
529-
let mut write_guard = r_writer.write().unwrap();
530-
*write_guard = i;
518+
const W: usize = if cfg!(target_pointer_width = "64") { 100 } else { 20 };
519+
const N: usize = 100;
531520

532-
let read_guard = RwLockWriteGuard::downgrade(write_guard);
533-
assert_eq!(*read_guard, i);
521+
// This test spawns `W` writer threads, where each will increment a counter `N` times, ensuring
522+
// that the value they wrote has not changed after downgrading.
534523

535-
// Wait for all readers to observe the new value.
536-
b_writer.wait();
537-
}
538-
});
524+
let rw = Arc::new(RwLock::new(0));
539525

540-
for _ in 0..R {
541-
let rwlock = rwlock.clone();
542-
let barrier = barrier.clone();
543-
thread::spawn(move || {
544-
// Every reader thread needs to observe every value up to `N`.
545-
for i in 0..N {
546-
let read_guard = rwlock.read().unwrap();
547-
assert_eq!(*read_guard, i);
548-
drop(read_guard);
549-
550-
// Wait for everyone to read and for the writer to change the value again.
551-
barrier.wait();
552-
553-
// Spin until the writer has changed the value.
554-
loop {
555-
let read_guard = rwlock.read().unwrap();
556-
assert!(*read_guard >= i);
557-
558-
if *read_guard > i {
559-
break;
560-
}
526+
// Spawn the writers that will do `W * N` operations and checks.
527+
let handles: Vec<_> = (0..W)
528+
.map(|_| {
529+
let rw = rw.clone();
530+
thread::spawn(move || {
531+
for _ in 0..N {
532+
// Increment the counter.
533+
let mut write_guard = rw.write().unwrap();
534+
*write_guard += 1;
535+
let cur_val = *write_guard;
536+
537+
// Downgrade the lock to read mode, where the value protected cannot be modified.
538+
let read_guard = RwLockWriteGuard::downgrade(write_guard);
539+
assert_eq!(cur_val, *read_guard);
561540
}
562-
}
563-
});
541+
})
542+
})
543+
.collect();
544+
545+
for handle in handles {
546+
handle.join().unwrap();
564547
}
548+
549+
assert_eq!(*rw.read().unwrap(), W * N);
565550
}
566551

567552
#[test]

std/src/sys/sync/rwlock/futex.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ impl RwLock {
195195
#[inline]
196196
pub unsafe fn downgrade(&self) {
197197
// Removes all write bits and adds a single read bit.
198-
let state = self.state.fetch_add(DOWNGRADE, Relaxed);
198+
let state = self.state.fetch_add(DOWNGRADE, Release);
199199
debug_assert!(is_write_locked(state), "RwLock must be write locked to call `downgrade`");
200200

201201
if has_readers_waiting(state) {

0 commit comments

Comments
 (0)