spin_seq/lib.rs
1#![no_std]
2//! This library provides the `SeqLock` type, which is a form of reader-writer
3//! lock that is heavily optimized for readers.
4//!
5//! In certain situations, `SeqLock` can be two orders of magnitude faster than
6//! the standard library `RwLock` type. Another advantage is that readers cannot
7//! starve writers: a writer will never block even if there are readers
8//! currently accessing the `SeqLock`.
9//!
10//! The only downside of `SeqLock` is that it only works on types that are
11//! `Copy`. This means that it is unsuitable for types that contains pointers
12//! to owned data.
13//!
14//! You should instead use a `RwLock` if you need
15//! a reader-writer lock for types that are not `Copy`.
16//!
17//! # Implementation
18//!
19//! The implementation is based on the [Linux seqlock type](http://lxr.free-electrons.com/source/include/linux/seqlock.h).
20//! Each `SeqLock` contains a sequence counter which tracks modifications to the
21//! locked data. The basic idea is that a reader will perform the following
22//! operations:
23//!
24//! 1. Read the sequence counter.
25//! 2. Read the data in the lock.
26//! 3. Read the sequence counter again.
27//! 4. If the two sequence counter values differ, loop back and try again.
28//! 5. Otherwise return the data that was read in step 2.
29//!
30//! Similarly, a writer will increment the sequence counter before and after
31//! writing to the data in the lock. Once a reader sees that the sequence
32//! counter has not changed while it was reading the data, it can safely return
33//! that data to the caller since it is known to be in a consistent state.
34//!
35//! # Examples
36//!
37//! ```
38//! use seqlock::SeqLock;
39//!
40//! let lock = SeqLock::new(5);
41//!
42//! {
43//! // Writing to the data involves a lock
44//! let mut w = lock.lock_write();
45//! *w += 1;
46//! assert_eq!(*w, 6);
47//! }
48//!
49//! {
50//! // Reading the data is a very fast operation
51//! let r = lock.read();
52//! assert_eq!(r, 6);
53//! }
54//! ```
55
56#![warn(missing_docs, rust_2018_idioms)]
57
58use spin::{Mutex, MutexGuard};
59use core::cell::UnsafeCell;
60use core::fmt;
61use core::mem::MaybeUninit;
62use core::ops::{Deref, DerefMut};
63use core::ptr;
64use core::sync::atomic::{fence, AtomicUsize, Ordering};
65
66/// A sequential lock
67pub struct SeqLock<T> {
68 seq: AtomicUsize,
69 data: UnsafeCell<T>,
70 mutex: Mutex<()>,
71}
72
73unsafe impl<T: Send> Send for SeqLock<T> {}
74unsafe impl<T: Send> Sync for SeqLock<T> {}
75
76/// RAII structure used to release the exclusive write access of a `SeqLock`
77/// when dropped.
78pub struct SeqLockGuard<'a, T> {
79 _guard: MutexGuard<'a, ()>,
80 seqlock: &'a SeqLock<T>,
81 seq: usize,
82}
83
84impl<T> SeqLock<T> {
85 #[inline]
86 fn end_write(&self, seq: usize) {
87 // Increment the sequence number again, which will make it even and
88 // allow readers to access the data. The release ordering ensures that
89 // all writes to the data are done before writing the sequence number.
90 self.seq.store(seq.wrapping_add(1), Ordering::Release);
91 }
92}
93
94impl<T: Copy> SeqLock<T> {
95 /// Creates a new SeqLock with the given initial value.
96 #[inline]
97 pub const fn new(val: T) -> SeqLock<T> {
98 SeqLock {
99 seq: AtomicUsize::new(0),
100 data: UnsafeCell::new(val),
101 mutex: Mutex::new(()),
102 }
103 }
104
105 /// Reads the value protected by the `SeqLock`.
106 ///
107 /// This operation is extremely fast since it only reads the `SeqLock`,
108 /// which allows multiple readers to read the value without interfering with
109 /// each other.
110 ///
111 /// If a writer is currently modifying the contained value then the calling
112 /// thread will block until the writer thread releases the lock.
113 ///
114 /// Attempting to read from a `SeqLock` while already holding a write lock
115 /// in the current thread will result in a deadlock.
116 #[inline]
117 pub fn read(&self) -> T {
118 loop {
119 // Load the first sequence number. The acquire ordering ensures that
120 // this is done before reading the data.
121 let seq1 = self.seq.load(Ordering::Acquire);
122
123 // If the sequence number is odd then it means a writer is currently
124 // modifying the value.
125 if seq1 & 1 != 0 {
126 // Yield to give the writer a chance to finish. Writing is
127 // expected to be relatively rare anyways so this isn't too
128 // performance critical.
129 core::hint::spin_loop();
130 continue;
131 }
132
133 // We need to use a volatile read here because the data may be
134 // concurrently modified by a writer. We also use MaybeUninit in
135 // case we read the data in the middle of a modification.
136 let result = unsafe { ptr::read_volatile(self.data.get() as *mut MaybeUninit<T>) };
137
138 // Make sure the seq2 read occurs after reading the data. What we
139 // ideally want is a load(Release), but the Release ordering is not
140 // available on loads.
141 fence(Ordering::Acquire);
142
143 // If the sequence number is the same then the data wasn't modified
144 // while we were reading it, and can be returned.
145 let seq2 = self.seq.load(Ordering::Relaxed);
146 if seq1 == seq2 {
147 return unsafe { result.assume_init() };
148 }
149 }
150 }
151
152 #[inline]
153 fn begin_write(&self) -> usize {
154 // Increment the sequence number. At this point, the number will be odd,
155 // which will force readers to spin until we finish writing.
156 let seq = self.seq.load(Ordering::Relaxed).wrapping_add(1);
157 self.seq.store(seq, Ordering::Relaxed);
158
159 // Make sure any writes to the data happen after incrementing the
160 // sequence number. What we ideally want is a store(Acquire), but the
161 // Acquire ordering is not available on stores.
162 fence(Ordering::Release);
163
164 seq
165 }
166
167 #[inline]
168 fn lock_guard<'a>(&'a self, guard: MutexGuard<'a, ()>) -> SeqLockGuard<'a, T> {
169 let seq = self.begin_write();
170 SeqLockGuard {
171 _guard: guard,
172 seqlock: self,
173 seq: seq,
174 }
175 }
176
177 /// Locks this `SeqLock` with exclusive write access, blocking the current
178 /// thread until it can be acquired.
179 ///
180 /// This function does not block while waiting for concurrent readers.
181 /// Instead, readers will detect the concurrent write and retry the read.
182 ///
183 /// Returns an RAII guard which will drop the write access of this `SeqLock`
184 /// when dropped.
185 #[inline]
186 pub fn lock_write(&self) -> SeqLockGuard<'_, T> {
187 self.lock_guard(self.mutex.lock())
188 }
189
190 /// Attempts to lock this `SeqLock` with exclusive write access.
191 ///
192 /// If the lock could not be acquired at this time, then `None` is returned.
193 /// Otherwise, an RAII guard is returned which will release the lock when
194 /// it is dropped.
195 ///
196 /// This function does not block.
197 #[inline]
198 pub fn try_lock_write(&self) -> Option<SeqLockGuard<'_, T>> {
199 self.mutex.try_lock().map(|g| self.lock_guard(g))
200 }
201
202 /// Consumes this `SeqLock`, returning the underlying data.
203 #[inline]
204 pub fn into_inner(self) -> T {
205 self.data.into_inner()
206 }
207
208 /// Returns a mutable reference to the underlying data.
209 ///
210 /// Since this call borrows the `SeqLock` mutably, no actual locking needs
211 /// to take place---the mutable borrow statically guarantees no locks exist.
212 #[inline]
213 pub fn get_mut(&mut self) -> &mut T {
214 unsafe { &mut *self.data.get() }
215 }
216}
217
218impl<T: Copy + Default> Default for SeqLock<T> {
219 #[inline]
220 fn default() -> SeqLock<T> {
221 SeqLock::new(Default::default())
222 }
223}
224
225impl<T: Copy + fmt::Debug> fmt::Debug for SeqLock<T> {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 write!(f, "SeqLock {{ data: {:?} }}", &self.read())
228 }
229}
230
231impl<'a, T: Copy + 'a> Deref for SeqLockGuard<'a, T> {
232 type Target = T;
233 #[inline]
234 fn deref(&self) -> &T {
235 unsafe { &*self.seqlock.data.get() }
236 }
237}
238
239impl<'a, T: Copy + 'a> DerefMut for SeqLockGuard<'a, T> {
240 #[inline]
241 fn deref_mut(&mut self) -> &mut T {
242 unsafe { &mut *self.seqlock.data.get() }
243 }
244}
245
246impl<T> Drop for SeqLockGuard<'_, T> {
247 #[inline]
248 fn drop(&mut self) {
249 self.seqlock.end_write(self.seq);
250 }
251}