starnix_sync/rw_seq_lock.rs
1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{
6 LockDepGuard, LockDepMutex, LockLevel, ThreadAffinity, ThreadAffinityGuard, assert_lock_level,
7};
8use fuchsia_rcu::RcuDroppable;
9use std::fmt;
10use std::ops::{Deref, DerefMut};
11use std::sync::atomic::{AtomicUsize, Ordering};
12
13/// Number of times a reader retries the lock-free path before falling back to taking the lock.
14const MAX_SPIN_COUNT: usize = 10;
15
16/// A sequence lock that combines a standard lock (like a Mutex) with a sequence
17/// counter. This allows lock-free concurrent reads by spinning if a write is in
18/// progress, while still enforcing mutually exclusive writes.
19///
20/// This lock is used to synchronize threads within the same address space.
21/// For synchronizing data across address spaces (e.g. sharing data with
22/// userspace via a VMO), see `//src/starnix/lib/seq_lock/`.
23#[derive(RcuDroppable)]
24pub struct RwSeqLock<L> {
25 /// The sequence number. An even value indicates the lock is not currently held
26 /// for writing, while an odd value indicates a write is in progress.
27 seq: AtomicUsize,
28 /// The underlying lock used to serialize writers.
29 lock: L,
30 /// Tracks if the current thread is currently holding this lock, preventing read_seq
31 /// from being called while holding the lock (which would lead to a livelock).
32 affinity: ThreadAffinity,
33}
34
35/// A guard that manages the sequence counter and wraps the underlying lock guard.
36///
37/// When this guard is dropped, the sequence counter is incremented, signaling to
38/// readers that the write operation has finished.
39pub struct RwSeqLockGuard<'a, G> {
40 seq: &'a AtomicUsize,
41 _affinity: ThreadAffinityGuard<'a>,
42 guard: G,
43}
44
45impl<L> RwSeqLock<L> {
46 /// Creates a new `RwSeqLock` wrapping the provided `lock`.
47 pub const fn new(lock: L) -> Self {
48 Self { seq: AtomicUsize::new(0), lock, affinity: ThreadAffinity::new() }
49 }
50}
51
52impl<T, L: LockLevel> RwSeqLock<LockDepMutex<T, L>> {
53 /// Executes the given closure `f` and returns its result, guaranteeing that
54 /// no writer was holding the lock while the closure was running.
55 ///
56 /// If a write is in progress, this method spins until the write finishes. If a write begins
57 /// while the closure is executing, the closure is retried. After `MAX_SPIN_COUNT` failed
58 /// attempts, the underlying lock is taken so that an active writer cannot starve readers.
59 ///
60 /// Because of that fallback, this method may acquire a lock of level `L`: the caller must be
61 /// allowed to acquire `L`, `f` must not acquire a lock at a level lower than or equal to `L`,
62 /// and `f` must not call `read_seq` on the same lock. All of this is checked on every call,
63 /// not only when the fallback triggers.
64 pub fn read_seq<R, F: Fn() -> R>(&self, f: F) -> R {
65 self.affinity.assert_not_attached();
66
67 // Check the lock ordering on every call, so that a violation doesn't depend on the timing
68 // of the writers.
69 let lock_level_guard = assert_lock_level::<L>();
70
71 for _ in 0..MAX_SPIN_COUNT {
72 let seq1 = self.seq.load(Ordering::Acquire);
73 if seq1 % 2 != 0 {
74 // A writer is currently holding the lock.
75 std::hint::spin_loop();
76 continue;
77 }
78
79 let result = f();
80
81 // A read memory barrier is required here to prevent the CPU from reordering
82 // the reads inside `f()` to happen AFTER `seq2` is loaded.
83 // `seq2.load(Ordering::Acquire)` only prevents subsequent accesses from moving
84 // before the load, but does not prevent preceding accesses from moving after it.
85 std::sync::atomic::fence(Ordering::Acquire);
86
87 let seq2 = self.seq.load(Ordering::Acquire);
88 if seq1 == seq2 {
89 // The sequence number hasn't changed, meaning no writer interfered.
90 return result;
91 }
92 }
93
94 // Writers are too active: take the underlying lock, which excludes them for the duration
95 // of the closure. Take the inner lock directly, as `lock()` would bump the sequence and
96 // force every other reader down this same path. Release the lock level token first: the
97 // acquisition does the same ordering check, and lockdep would otherwise report a
98 // self-deadlock on level `L`.
99 drop(lock_level_guard);
100 let _guard = self.lock.lock();
101 f()
102 }
103
104 /// Acquires the underlying lock for writing.
105 ///
106 /// This increments the sequence counter (making it odd) to indicate to readers
107 /// that a write is in progress. When the returned guard is dropped, the sequence
108 /// counter is incremented again (making it even).
109 pub fn lock(&self) -> RwSeqLockGuard<'_, LockDepGuard<'_, T>> {
110 let guard = self.lock.lock();
111 // Increment the sequence to an odd number, notifying readers that writing has
112 // started.
113 let prev = self.seq.fetch_add(1, Ordering::Release);
114 debug_assert!(prev % 2 == 0, "RwSeqLock sequence should be even before locking");
115 RwSeqLockGuard { seq: &self.seq, _affinity: self.affinity.attach(), guard }
116 }
117}
118
119impl<'a, G> Drop for RwSeqLockGuard<'a, G> {
120 fn drop(&mut self) {
121 // Increment the sequence to an even number, notifying readers that writing is
122 // finished.
123 let prev = self.seq.fetch_add(1, Ordering::Release);
124 debug_assert!(prev % 2 != 0, "RwSeqLock sequence should be odd before unlocking");
125 }
126}
127
128impl<'a, G: Deref> Deref for RwSeqLockGuard<'a, G> {
129 type Target = G::Target;
130 fn deref(&self) -> &Self::Target {
131 &self.guard
132 }
133}
134
135impl<'a, G: DerefMut> DerefMut for RwSeqLockGuard<'a, G> {
136 fn deref_mut(&mut self) -> &mut Self::Target {
137 &mut self.guard
138 }
139}
140
141impl<L: Default> Default for RwSeqLock<L> {
142 fn default() -> Self {
143 Self::new(L::default())
144 }
145}
146
147impl<L: fmt::Debug> fmt::Debug for RwSeqLock<L> {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.debug_struct("RwSeqLock").field("lock", &self.lock).finish()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::lock_ordering;
157 use std::sync::atomic::{AtomicU32, Ordering};
158
159 lock_ordering! {
160 Unlocked => TestLevel,
161 }
162
163 #[test]
164 fn test_rw_seq_lock() {
165 let lock: RwSeqLock<LockDepMutex<u32, TestLevel>> = RwSeqLock::new(0.into());
166 let data = AtomicU32::new(0);
167
168 let read_val = lock.read_seq(|| data.load(Ordering::Relaxed));
169 assert_eq!(read_val, 0);
170
171 {
172 let mut guard = lock.lock();
173 *guard = 1;
174 data.store(1, Ordering::Relaxed);
175 }
176
177 let read_val2 = lock.read_seq(|| data.load(Ordering::Relaxed));
178 assert_eq!(read_val2, 1);
179 }
180
181 #[test]
182 fn test_rw_seq_lock_read_falls_back_to_locking() {
183 use std::sync::{Arc, Barrier};
184 use std::thread;
185 use std::time::Duration;
186
187 struct TestData {
188 lock: RwSeqLock<LockDepMutex<(), TestLevel>>,
189 val: AtomicU32,
190 barrier: Barrier,
191 }
192
193 let data = Arc::new(TestData {
194 lock: RwSeqLock::new(Default::default()),
195 val: AtomicU32::new(0),
196 barrier: Barrier::new(2),
197 });
198
199 // Hold the write lock for as long as the reader runs, so that every lock-free attempt
200 // fails and the reader is forced down the fallback path.
201 let guard = data.lock.lock();
202
203 let reader = thread::spawn({
204 let data = data.clone();
205 move || {
206 data.barrier.wait();
207 data.lock.read_seq(|| data.val.load(Ordering::Relaxed))
208 }
209 });
210
211 data.barrier.wait();
212 // Leave the reader enough time to exhaust its attempts and block on the lock. The test
213 // stays correct otherwise, it just stops covering the fallback path.
214 thread::sleep(Duration::from_millis(50));
215
216 data.val.store(42, Ordering::Relaxed);
217 drop(guard);
218
219 assert_eq!(reader.join().unwrap(), 42);
220 }
221
222 #[test]
223 fn test_rw_seq_lock_concurrent() {
224 use std::sync::Arc;
225 use std::thread;
226
227 struct TestData {
228 lock: RwSeqLock<LockDepMutex<(), TestLevel>>,
229 val1: AtomicU32,
230 val2: AtomicU32,
231 }
232
233 let data = Arc::new(TestData {
234 lock: RwSeqLock::new(Default::default()),
235 val1: AtomicU32::new(0),
236 val2: AtomicU32::new(0),
237 });
238
239 let mut handles = vec![];
240
241 // Spawn writers
242 for i in 0..4 {
243 let data = data.clone();
244 handles.push(thread::spawn(move || {
245 for j in 0..1000 {
246 let val = i * 1000 + j;
247 let _guard = data.lock.lock();
248 data.val1.store(val, Ordering::Relaxed);
249 thread::yield_now();
250 data.val2.store(val, Ordering::Relaxed);
251 }
252 }));
253 }
254
255 // Spawn readers
256 for _ in 0..4 {
257 let data = data.clone();
258 handles.push(thread::spawn(move || {
259 for _ in 0..1000 {
260 let (v1, v2) = data.lock.read_seq(|| {
261 let v1 = data.val1.load(Ordering::Relaxed);
262 thread::yield_now();
263 let v2 = data.val2.load(Ordering::Relaxed);
264 (v1, v2)
265 });
266 assert_eq!(v1, v2);
267 }
268 }));
269 }
270
271 for handle in handles {
272 handle.join().unwrap();
273 }
274 }
275}