Skip to main content

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}