low_latency_data_structures/seqlock/
lock.rs1use std::cell::UnsafeCell;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU64, Ordering, fence};
4
5use crate::seqlock::reader::Reader;
6use crate::seqlock::writer::Writer;
7
8pub fn new<T: bytemuck::AnyBitPattern>(initial_value: T) -> (Writer<T>, Reader<T>) {
24 let sl = Arc::new(SeqLock {
25 seq: AtomicU64::new(0),
26 data: UnsafeCell::new(initial_value),
27 });
28 let writer = Writer::new(sl.clone());
29 let reader = Reader::new(sl);
30 (writer, reader)
31}
32
33#[repr(C, align(128))]
35pub(super) struct SeqLock<T: bytemuck::AnyBitPattern> {
36 seq: AtomicU64,
37 data: UnsafeCell<T>,
38}
39
40unsafe impl<T: bytemuck::AnyBitPattern> Sync for SeqLock<T> {}
47
48impl<T: bytemuck::AnyBitPattern> SeqLock<T> {
49 #[inline]
50 pub(super) fn write(&self, value: T) {
51 let s = self.seq.load(Ordering::Relaxed);
52 self.seq.store(s.wrapping_add(1), Ordering::Relaxed);
53 fence(Ordering::Release);
55 unsafe { self.data.get().write_volatile(value) };
59 self.seq.store(s.wrapping_add(2), Ordering::Release);
60 }
61
62 #[inline]
63 pub(super) fn read(&self) -> T {
64 loop {
65 let s1 = self.seq.load(Ordering::Acquire);
66 if s1 & 1 == 1 {
67 continue;
68 }
69 let read_attempt = unsafe { self.data.get().read_volatile() };
72 fence(Ordering::Acquire);
74 let s2 = self.seq.load(Ordering::Relaxed);
75 if s1 == s2 {
76 return read_attempt;
77 }
78 }
79 }
80}
81
82#[cfg(feature = "tests_basic")]
83#[cfg(test)]
84mod tests {
85 use std::sync::Barrier;
86 use std::thread;
87
88 use super::*;
89
90 #[test]
91 fn first_read_returns_initial_value() {
92 for init_value in [42, 1, 23] {
93 let (_, reader) = new(init_value);
94 assert_eq!(reader.read(), init_value);
95 }
96 }
97
98 #[test]
99 fn single_thread_reads_what_it_has_written() {
100 let (writer, reader) = new(0);
101 for value in [42, 1, 23] {
102 writer.write(value);
103 assert_eq!(reader.read(), value);
104 }
105 }
106
107 #[test]
108 fn single_writer_multi_reader() {
109 const READERS: usize = 3;
110 const VALUES: [u64; 4] = [1, 4, 7, 9];
111 let (writer, reader) = new(0);
112 let reader = Arc::new(reader);
113 let barrier = Arc::new(Barrier::new(READERS + 1));
114 let writer_h = {
115 let barrier = barrier.clone();
116 thread::spawn(move || {
117 barrier.wait();
119 for value in VALUES {
120 writer.write(value);
121 barrier.wait();
123 }
124 })
125 };
126 let readers_hs: Vec<_> = (0..READERS)
127 .map(|_| {
128 let barrier = barrier.clone();
129 let reader = reader.clone();
130 thread::spawn(move || {
131 let mut collected: Vec<u64> = Vec::with_capacity(VALUES.len() + 1);
132 loop {
133 if collected.len() == VALUES.len() + 1 {
134 break;
135 }
136 let item = reader.read();
137 if let Some(&last) = collected.last()
138 && last == item
139 {
140 continue;
141 } else {
142 collected.push(item);
143 barrier.wait();
144 }
145 }
146 collected
147 })
148 })
149 .collect();
150 writer_h.join().unwrap();
151 let readers_results: Vec<Vec<u64>> =
152 readers_hs.into_iter().map(|h| h.join().unwrap()).collect();
153 assert!(
154 readers_results
155 .iter()
156 .all(|result| result.len() == VALUES.len() + 1 && &result[1..] == &VALUES[..])
157 );
158 }
159}
160
161#[cfg(test)]
162#[cfg(feature = "tests_dhat")]
163mod tests_dhat {
164 use std::hint::black_box;
165
166 use super::*;
167
168 #[test]
169 fn hot_path_without_allocations() {
170 let _profiler = dhat::Profiler::builder().testing().build();
171 let (writer, reader) = new(0);
172 const ITERS: u64 = 10_000_000;
173
174 let before = dhat::HeapStats::get();
175 for i in 0..ITERS {
176 writer.write(black_box(i));
177 black_box(reader.read());
178 }
179 let after = dhat::HeapStats::get();
180
181 assert_eq!(before, after);
182 }
183}