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::{LockDepGuard, LockDepMutex, LockLevel, ThreadAffinity, ThreadAffinityGuard};
6use std::fmt;
7use std::ops::{Deref, DerefMut};
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10/// A sequence lock that combines a standard lock (like a Mutex) with a sequence
11/// counter. This allows lock-free concurrent reads by spinning if a write is in
12/// progress, while still enforcing mutually exclusive writes.
13///
14/// This lock is used to synchronize threads within the same address space.
15/// For synchronizing data across address spaces (e.g. sharing data with
16/// userspace via a VMO), see `//src/starnix/lib/seq_lock/`.
17pub struct RwSeqLock<L> {
18    /// The sequence number. An even value indicates the lock is not currently held
19    /// for writing, while an odd value indicates a write is in progress.
20    seq: AtomicUsize,
21    /// The underlying lock used to serialize writers.
22    lock: L,
23    /// Tracks if the current thread is currently holding this lock, preventing read_seq
24    /// from being called while holding the lock (which would lead to a livelock).
25    affinity: ThreadAffinity,
26}
27
28/// A guard that manages the sequence counter and wraps the underlying lock guard.
29///
30/// When this guard is dropped, the sequence counter is incremented, signaling to
31/// readers that the write operation has finished.
32pub struct RwSeqLockGuard<'a, G> {
33    seq: &'a AtomicUsize,
34    _affinity: ThreadAffinityGuard<'a>,
35    guard: G,
36}
37
38impl<L> RwSeqLock<L> {
39    /// Creates a new `RwSeqLock` wrapping the provided `lock`.
40    pub const fn new(lock: L) -> Self {
41        Self { seq: AtomicUsize::new(0), lock, affinity: ThreadAffinity::new() }
42    }
43
44    /// Executes the given closure `f` and returns its result, guaranteeing that
45    /// no writer was holding the lock while the closure was running.
46    ///
47    /// If a write is in progress, this method will spin until the write finishes.
48    /// If a write begins while the closure is executing, the closure will be retried.
49    pub fn read_seq<R, F: Fn() -> R>(&self, f: F) -> R {
50        self.affinity.assert_not_attached();
51        loop {
52            let seq1 = self.seq.load(Ordering::Acquire);
53            if seq1 % 2 != 0 {
54                // A writer is currently holding the lock.
55                std::hint::spin_loop();
56                continue;
57            }
58
59            let result = f();
60
61            // A read memory barrier is required here to prevent the CPU from reordering
62            // the reads inside `f()` to happen AFTER `seq2` is loaded.
63            // `seq2.load(Ordering::Acquire)` only prevents subsequent accesses from moving
64            // before the load, but does not prevent preceding accesses from moving after it.
65            std::sync::atomic::fence(Ordering::Acquire);
66
67            let seq2 = self.seq.load(Ordering::Acquire);
68            if seq1 == seq2 {
69                // The sequence number hasn't changed, meaning no writer interfered.
70                return result;
71            }
72        }
73    }
74}
75
76impl<T, L: LockLevel> RwSeqLock<LockDepMutex<T, L>> {
77    /// Acquires the underlying lock for writing.
78    ///
79    /// This increments the sequence counter (making it odd) to indicate to readers
80    /// that a write is in progress. When the returned guard is dropped, the sequence
81    /// counter is incremented again (making it even).
82    pub fn lock(&self) -> RwSeqLockGuard<'_, LockDepGuard<'_, T>> {
83        let guard = self.lock.lock();
84        // Increment the sequence to an odd number, notifying readers that writing has
85        // started.
86        let prev = self.seq.fetch_add(1, Ordering::Release);
87        debug_assert!(prev % 2 == 0, "RwSeqLock sequence should be even before locking");
88        RwSeqLockGuard { seq: &self.seq, _affinity: self.affinity.attach(), guard }
89    }
90}
91
92impl<'a, G> Drop for RwSeqLockGuard<'a, G> {
93    fn drop(&mut self) {
94        // Increment the sequence to an even number, notifying readers that writing is
95        // finished.
96        let prev = self.seq.fetch_add(1, Ordering::Release);
97        debug_assert!(prev % 2 != 0, "RwSeqLock sequence should be odd before unlocking");
98    }
99}
100
101impl<'a, G: Deref> Deref for RwSeqLockGuard<'a, G> {
102    type Target = G::Target;
103    fn deref(&self) -> &Self::Target {
104        &self.guard
105    }
106}
107
108impl<'a, G: DerefMut> DerefMut for RwSeqLockGuard<'a, G> {
109    fn deref_mut(&mut self) -> &mut Self::Target {
110        &mut self.guard
111    }
112}
113
114impl<L: Default> Default for RwSeqLock<L> {
115    fn default() -> Self {
116        Self::new(L::default())
117    }
118}
119
120impl<L: fmt::Debug> fmt::Debug for RwSeqLock<L> {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.debug_struct("RwSeqLock").field("lock", &self.lock).finish()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::{Unlocked, lock_ordering};
130    use std::sync::atomic::{AtomicU32, Ordering};
131
132    lock_ordering! {
133        Unlocked => TestLevel,
134    }
135
136    #[test]
137    fn test_rw_seq_lock() {
138        let lock: RwSeqLock<LockDepMutex<u32, TestLevel>> = RwSeqLock::new(0.into());
139        let data = AtomicU32::new(0);
140
141        let read_val = lock.read_seq(|| data.load(Ordering::Relaxed));
142        assert_eq!(read_val, 0);
143
144        {
145            let mut guard = lock.lock();
146            *guard = 1;
147            data.store(1, Ordering::Relaxed);
148        }
149
150        let read_val2 = lock.read_seq(|| data.load(Ordering::Relaxed));
151        assert_eq!(read_val2, 1);
152    }
153
154    #[test]
155    fn test_rw_seq_lock_concurrent() {
156        use std::sync::Arc;
157        use std::thread;
158
159        struct TestData {
160            lock: RwSeqLock<LockDepMutex<(), TestLevel>>,
161            val1: AtomicU32,
162            val2: AtomicU32,
163        }
164
165        let data = Arc::new(TestData {
166            lock: RwSeqLock::new(Default::default()),
167            val1: AtomicU32::new(0),
168            val2: AtomicU32::new(0),
169        });
170
171        let mut handles = vec![];
172
173        // Spawn writers
174        for i in 0..4 {
175            let data = data.clone();
176            handles.push(thread::spawn(move || {
177                for j in 0..1000 {
178                    let val = i * 1000 + j;
179                    let _guard = data.lock.lock();
180                    data.val1.store(val, Ordering::Relaxed);
181                    thread::yield_now();
182                    data.val2.store(val, Ordering::Relaxed);
183                }
184            }));
185        }
186
187        // Spawn readers
188        for _ in 0..4 {
189            let data = data.clone();
190            handles.push(thread::spawn(move || {
191                for _ in 0..1000 {
192                    let (v1, v2) = data.lock.read_seq(|| {
193                        let v1 = data.val1.load(Ordering::Relaxed);
194                        thread::yield_now();
195                        let v2 = data.val2.load(Ordering::Relaxed);
196                        (v1, v2)
197                    });
198                    assert_eq!(v1, v2);
199                }
200            }));
201        }
202
203        for handle in handles {
204            handle.join().unwrap();
205        }
206    }
207}