Skip to content

Commit f804164

Browse files
committed
add downgrade to futex implementation
1 parent 572aded commit f804164

File tree

1 file changed

+47
-5
lines changed

1 file changed

+47
-5
lines changed

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

+47-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct RwLock {
1818
const READ_LOCKED: Primitive = 1;
1919
const MASK: Primitive = (1 << 30) - 1;
2020
const WRITE_LOCKED: Primitive = MASK;
21+
const DOWNGRADE: Primitive = READ_LOCKED.wrapping_sub(WRITE_LOCKED); // READ_LOCKED - WRITE_LOCKED
2122
const MAX_READERS: Primitive = MASK - 1;
2223
const READERS_WAITING: Primitive = 1 << 30;
2324
const WRITERS_WAITING: Primitive = 1 << 31;
@@ -53,6 +54,24 @@ fn is_read_lockable(state: Primitive) -> bool {
5354
state & MASK < MAX_READERS && !has_readers_waiting(state) && !has_writers_waiting(state)
5455
}
5556

57+
#[inline]
58+
fn is_read_lockable_after_wakeup(state: Primitive) -> bool {
59+
// We make a special case for checking if we can read-lock _after_ a reader thread that went to
60+
// sleep has been woken up by a call to `downgrade`.
61+
//
62+
// `downgrade` will wake up all readers and place the lock in read mode. Thus, there should be
63+
// no readers waiting and the lock should be read-locked (not write-locked or unlocked).
64+
//
65+
// Note that we do not check if any writers are waiting. This is because a call to `downgrade`
66+
// implies that the caller wants other readers to read the value protected by the lock. If we
67+
// did not allow readers to acquire the lock before writers after a `downgrade`, then only the
68+
// original writer would be able to read the value, thus defeating the purpose of `downgrade`.
69+
state & MASK < MAX_READERS
70+
&& !has_readers_waiting(state)
71+
&& !is_write_locked(state)
72+
&& !is_unlocked(state)
73+
}
74+
5675
#[inline]
5776
fn has_reached_max_readers(state: Primitive) -> bool {
5877
state & MASK == MAX_READERS
@@ -84,6 +103,9 @@ impl RwLock {
84103
}
85104
}
86105

106+
/// # Safety
107+
///
108+
/// The `RwLock` must be read-locked (N readers) in order to call this.
87109
#[inline]
88110
pub unsafe fn read_unlock(&self) {
89111
let state = self.state.fetch_sub(READ_LOCKED, Release) - READ_LOCKED;
@@ -100,11 +122,13 @@ impl RwLock {
100122

101123
#[cold]
102124
fn read_contended(&self) {
125+
let mut has_slept = false;
103126
let mut state = self.spin_read();
104127

105128
loop {
106-
// If we can lock it, lock it.
107-
if is_read_lockable(state) {
129+
// If we have just been woken up, first check for a `downgrade` call.
130+
// Otherwise, if we can read-lock it, lock it.
131+
if (has_slept && is_read_lockable_after_wakeup(state)) || is_read_lockable(state) {
108132
match self.state.compare_exchange_weak(state, state + READ_LOCKED, Acquire, Relaxed)
109133
{
110134
Ok(_) => return, // Locked!
@@ -116,9 +140,7 @@ impl RwLock {
116140
}
117141

118142
// Check for overflow.
119-
if has_reached_max_readers(state) {
120-
panic!("too many active read locks on RwLock");
121-
}
143+
assert!(!has_reached_max_readers(state), "too many active read locks on RwLock");
122144

123145
// Make sure the readers waiting bit is set before we go to sleep.
124146
if !has_readers_waiting(state) {
@@ -132,6 +154,7 @@ impl RwLock {
132154

133155
// Wait for the state to change.
134156
futex_wait(&self.state, state | READERS_WAITING, None);
157+
has_slept = true;
135158

136159
// Spin again after waking up.
137160
state = self.spin_read();
@@ -152,6 +175,9 @@ impl RwLock {
152175
}
153176
}
154177

178+
/// # Safety
179+
///
180+
/// The `RwLock` must be write-locked (single writer) in order to call this.
155181
#[inline]
156182
pub unsafe fn write_unlock(&self) {
157183
let state = self.state.fetch_sub(WRITE_LOCKED, Release) - WRITE_LOCKED;
@@ -163,6 +189,22 @@ impl RwLock {
163189
}
164190
}
165191

192+
/// # Safety
193+
///
194+
/// The `RwLock` must be write-locked (single writer) in order to call this.
195+
#[inline]
196+
pub unsafe fn downgrade(&self) {
197+
// Removes all write bits and adds a single read bit.
198+
let state = self.state.fetch_add(DOWNGRADE, Relaxed);
199+
debug_assert!(is_write_locked(state), "RwLock must be write locked to call `downgrade`");
200+
201+
if has_readers_waiting(state) {
202+
// Since we had the exclusive lock, nobody else can unset this bit.
203+
self.state.fetch_sub(READERS_WAITING, Relaxed);
204+
futex_wake_all(&self.state);
205+
}
206+
}
207+
166208
#[cold]
167209
fn write_contended(&self) {
168210
let mut state = self.spin_write();

0 commit comments

Comments
 (0)