fuel_core_services/
seqlock.rs1use std::{
5 cell::UnsafeCell,
6 panic::UnwindSafe,
7 sync::atomic::{
8 fence,
9 AtomicU64,
10 Ordering,
11 },
12};
13
14#[derive(Debug)]
17pub struct SeqLock<T: Copy> {
18 sequence: AtomicU64,
19 data: UnsafeCell<T>,
20}
21
22unsafe impl<T: Send + Copy> Sync for SeqLock<T> {}
23
24#[derive(Debug)]
28pub struct SeqLockWriter<T: Copy> {
29 lock: std::sync::Arc<SeqLock<T>>,
30}
31
32impl<T: Copy> SeqLockWriter<T> {
33 pub fn write<F>(&self, f: F)
35 where
36 F: FnOnce(&mut T) + UnwindSafe,
37 {
38 let lock = &self.lock;
39
40 lock.sequence.fetch_add(1, Ordering::AcqRel);
42 fence(Ordering::Acquire);
44
45 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
49 let data = &mut *lock.data.get();
50 f(data);
51 }));
52
53 fence(Ordering::Release);
55 lock.sequence.fetch_add(1, Ordering::Release);
57
58 if let Err(e) = result {
60 std::panic::resume_unwind(e);
61 }
62 }
63}
64
65#[derive(Clone, Debug)]
68pub struct SeqLockReader<T: Copy> {
69 lock: std::sync::Arc<SeqLock<T>>,
70}
71
72impl<T: Copy> SeqLockReader<T> {
73 pub fn read(&self) -> T {
75 let lock = &self.lock;
76
77 loop {
78 let start = lock.sequence.load(Ordering::Acquire);
80
81 if start % 2 != 0 {
83 std::thread::yield_now();
84 continue;
85 }
86
87 fence(Ordering::Acquire);
89
90 let data = unsafe { *lock.data.get() };
93
94 fence(Ordering::Acquire);
96
97 let end = lock.sequence.load(Ordering::Acquire);
99
100 if start == end && start % 2 == 0 {
102 return data;
103 }
104 }
105 }
106}
107
108impl<T: Copy> SeqLock<T> {
109 #[allow(clippy::new_ret_no_self)]
118 pub unsafe fn new(data: T) -> (SeqLockWriter<T>, SeqLockReader<T>) {
119 let lock = Self {
120 sequence: AtomicU64::new(0),
121 data: UnsafeCell::new(data),
122 };
123 let shared = std::sync::Arc::new(lock);
124 (
125 SeqLockWriter {
126 lock: std::sync::Arc::clone(&shared),
127 },
128 SeqLockReader { lock: shared },
129 )
130 }
131}
132
133#[allow(non_snake_case)]
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use std::thread;
138
139 #[test]
140 fn test_seqlock__provides_correct_values_in_order() {
141 let (writer, reader) = unsafe { SeqLock::new(42) };
142 let iterations = 100;
143
144 let writer = {
145 thread::spawn(move || {
146 for i in 0..iterations {
147 writer.write(|data| *data = i);
148 }
149 })
150 };
151
152 let reader = {
153 let lock = reader.clone();
154 thread::spawn(move || {
155 let seen = 0;
156
157 for _ in 0..iterations {
158 let value = lock.read();
159 assert!(value >= seen);
160 }
161 })
162 };
163
164 writer.join().unwrap();
165 reader.join().unwrap();
166 }
167
168 #[test]
169 fn test_seqlock__single_threaded() {
170 let (writer, reader) = unsafe { SeqLock::new(42) };
171
172 writer.write(|data| {
173 *data = 100;
174 });
175
176 let value = reader.read();
177 assert_eq!(value, 100);
178 }
179}