1use zerocopy::{FromBytes, Immutable, IntoBytes};
6
7use crate::common::{SYNC_OPT_ACQ_REL_OPS, SYNC_OPT_FENCE, SYNC_OPT_NONE, SyncOpt};
8use crate::copy::WellDefinedCopyable;
9use crate::seqlock::{SeqLock, WriteGuard};
10
11#[repr(transparent)]
17pub struct SeqLockPayload<
18 T: Copy + FromBytes + IntoBytes + Immutable,
19 const LOCK_SYNC_OPT: u8 = SYNC_OPT_FENCE,
20> {
21 payload: WellDefinedCopyable<T>,
24}
25
26impl<T: Copy + FromBytes + IntoBytes + Immutable, const LOCK_SYNC_OPT: u8>
27 SeqLockPayload<T, LOCK_SYNC_OPT>
28{
29 pub const COPY_SYNC_OPT: SyncOpt = SeqLock::<LOCK_SYNC_OPT>::COPY_WRAPPER_SYNC_OPT;
32
33 #[inline]
35 pub const fn new(instance: T) -> Self {
36 const {
40 assert!(matches!(Self::COPY_SYNC_OPT, SyncOpt::AcqRelOps | SyncOpt::None));
41 };
42 Self { payload: WellDefinedCopyable::new(instance) }
43 }
44
45 #[inline]
48 pub fn read(&self, dst: &mut T) {
49 if const { matches!(Self::COPY_SYNC_OPT, SyncOpt::AcqRelOps) } {
50 self.payload.read::<SYNC_OPT_ACQ_REL_OPS>(dst);
51 } else {
52 self.payload.read::<SYNC_OPT_NONE>(dst);
53 }
54 }
55
56 #[inline]
59 pub fn update(&self, _guard: &WriteGuard<'_, LOCK_SYNC_OPT>, src: &T) {
60 if const { matches!(Self::COPY_SYNC_OPT, SyncOpt::AcqRelOps) } {
61 self.payload.update::<SYNC_OPT_ACQ_REL_OPS>(src);
62 } else {
63 self.payload.update::<SYNC_OPT_NONE>(src);
64 }
65 }
66
67 #[inline]
72 #[must_use]
73 pub fn begin_in_place_update(&self, _guard: &WriteGuard<'_, LOCK_SYNC_OPT>) -> *mut T {
74 const {
75 assert!(
76 LOCK_SYNC_OPT == SYNC_OPT_FENCE,
77 "In-place updates can only be performed when using fence synchronization"
78 );
79 };
80 self.payload.instance.get()
81 }
82
83 #[inline]
89 #[must_use]
90 pub const fn unsynchronized_get(&self, _guard: &WriteGuard<'_, LOCK_SYNC_OPT>) -> *const T {
91 self.payload.unsynchronized_get()
92 }
93}
94
95impl<T: Copy + FromBytes + IntoBytes + Immutable + Default, const LOCK_SYNC_OPT: u8> Default
96 for SeqLockPayload<T, LOCK_SYNC_OPT>
97{
98 fn default() -> Self {
99 Self::new(T::default())
100 }
101}
102
103zr::static_assert!(size_of::<SeqLockPayload<u64>>() == size_of::<u64>());
104zr::static_assert!(align_of::<SeqLockPayload<u64>>() == align_of::<u64>());
105zr::static_assert!(size_of::<SeqLockPayload<[u64; 8]>>() == 64);
106zr::static_assert!(align_of::<SeqLockPayload<[u64; 8]>>() == align_of::<u64>());
107zr::static_assert!(
108 size_of::<SeqLockPayload<[u64; 3], SYNC_OPT_ACQ_REL_OPS>>() == size_of::<[u64; 3]>()
109);
110zr::static_assert!(
111 align_of::<SeqLockPayload<[u64; 3], SYNC_OPT_ACQ_REL_OPS>>() == align_of::<[u64; 3]>()
112);
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[repr(C)]
119 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, FromBytes, IntoBytes, Immutable)]
120 struct TestTransform {
121 a_offset: i64,
122 b_offset: i64,
123 numerator: u32,
124 denominator: u32,
125 }
126
127 impl TestTransform {
128 fn from_generation(generation: u64) -> Self {
129 Self {
130 a_offset: generation as i64,
131 b_offset: !(generation as i64),
132 numerator: generation as u32,
133 denominator: !(generation as u32),
134 }
135 }
136
137 fn is_consistent(&self) -> bool {
138 (self.a_offset == !self.b_offset)
139 && (self.numerator == !self.denominator)
140 && (self.numerator == self.a_offset as u32)
141 }
142 }
143
144 #[repr(C)]
145 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, FromBytes, IntoBytes, Immutable)]
146 struct TestParams {
147 values: [u64; 8],
148 }
149
150 impl TestParams {
151 fn from_generation(generation: u64) -> Self {
152 let mut values = [0; 8];
153 for (i, value) in values.iter_mut().enumerate() {
154 *value = generation.wrapping_add(i as u64);
155 }
156 Self { values }
157 }
158
159 fn is_consistent(&self) -> bool {
160 self.values
161 .iter()
162 .enumerate()
163 .all(|(i, value)| *value == self.values[0].wrapping_add(i as u64))
164 }
165 }
166
167 zr::static_assert!(size_of::<TestTransform>() == 24);
168 zr::static_assert!(size_of::<TestParams>() == 64);
169
170 type AcqRelPayload<T> = SeqLockPayload<T, SYNC_OPT_ACQ_REL_OPS>;
171
172 #[test]
173 fn test_copy_sync_opt() {
174 assert_eq!(SeqLockPayload::<u64>::COPY_SYNC_OPT, SyncOpt::None);
175 assert_eq!(AcqRelPayload::<u64>::COPY_SYNC_OPT, SyncOpt::AcqRelOps);
176
177 assert_eq!(
178 SeqLockPayload::<u64>::COPY_SYNC_OPT,
179 SeqLock::<SYNC_OPT_FENCE>::COPY_WRAPPER_SYNC_OPT
180 );
181 assert_eq!(
182 AcqRelPayload::<u64>::COPY_SYNC_OPT,
183 SeqLock::<SYNC_OPT_ACQ_REL_OPS>::COPY_WRAPPER_SYNC_OPT
184 );
185 }
186
187 #[test]
188 fn test_layout() {
189 assert_eq!(size_of::<SeqLockPayload<TestTransform>>(), size_of::<TestTransform>());
190 assert_eq!(align_of::<SeqLockPayload<TestTransform>>(), align_of::<TestTransform>());
191 assert_eq!(size_of::<SeqLockPayload<TestParams>>(), size_of::<TestParams>());
192 assert_eq!(align_of::<SeqLockPayload<TestParams>>(), align_of::<TestParams>());
193 }
194
195 #[test]
196 fn test_read_update_single_threaded() {
197 let lock: SeqLock = SeqLock::new();
198 let payload = SeqLockPayload::<TestTransform>::default();
199
200 let mut dst = TestTransform::from_generation(99);
201 payload.read(&mut dst);
202 assert_eq!(dst, TestTransform::default());
203
204 let expected = TestTransform::from_generation(42);
205 {
206 let guard = lock.acquire();
207 payload.update(&guard, &expected);
208
209 assert_eq!(unsafe { *payload.unsynchronized_get(&guard) }, expected);
212 }
213
214 let mut observed = TestTransform::default();
215 lock.read_transaction(|| payload.read(&mut observed));
216 assert_eq!(observed, expected);
217 }
218
219 #[test]
220 fn test_read_update_acq_rel() {
221 let lock = SeqLock::<SYNC_OPT_ACQ_REL_OPS>::new();
222 let payload = AcqRelPayload::<TestParams>::new(TestParams::from_generation(1));
223
224 let mut dst = TestParams::default();
225 payload.read(&mut dst);
226 assert_eq!(dst, TestParams::from_generation(1));
227
228 let expected = TestParams::from_generation(9999);
229 {
230 let guard = lock.acquire();
231 payload.update(&guard, &expected);
232 }
233
234 let mut observed = TestParams::default();
235 lock.read_transaction(|| payload.read(&mut observed));
236 assert_eq!(observed, expected);
237 }
238
239 #[test]
240 fn test_in_place_update() {
241 use core::sync::atomic::{AtomicI64, Ordering};
242
243 let lock: SeqLock = SeqLock::new();
244 let payload = SeqLockPayload::<TestTransform>::default();
245
246 {
247 let guard = lock.acquire();
248 let ptr = payload.begin_in_place_update(&guard);
249
250 unsafe {
253 AtomicI64::from_ptr(core::ptr::addr_of_mut!((*ptr).a_offset))
254 .store(1234, Ordering::Relaxed);
255 }
256 }
257
258 let mut observed = TestTransform::default();
259 lock.read_transaction(|| payload.read(&mut observed));
260 assert_eq!(observed.a_offset, 1234);
261 }
262
263 #[test]
264 fn test_concurrent_readers_writers() {
265 use std::sync::Arc;
266 use std::thread;
267
268 struct Shared {
269 lock: SeqLock,
270 transform: SeqLockPayload<TestTransform>,
271 params: SeqLockPayload<TestParams>,
272 }
273
274 const ITERATIONS: u64 = 5000;
275
276 let shared = Arc::new(Shared {
277 lock: SeqLock::new(),
278 transform: SeqLockPayload::new(TestTransform::from_generation(0)),
279 params: SeqLockPayload::new(TestParams::from_generation(0)),
280 });
281
282 let writer = {
283 let shared = shared.clone();
284 thread::spawn(move || {
285 for i in 1..=ITERATIONS {
286 let guard = shared.lock.acquire();
287 shared.transform.update(&guard, &TestTransform::from_generation(i));
288 shared.params.update(&guard, &TestParams::from_generation(i));
289 }
290 })
291 };
292
293 let mut successful_transactions: u64 = 0;
294 let mut observed: u64 = 0;
295 let mut transform = TestTransform::default();
296 let mut params = TestParams::default();
297 while observed < ITERATIONS {
298 let token = shared.lock.begin_read_transaction();
299 shared.transform.read(&mut transform);
300 shared.params.read(&mut params);
301 if shared.lock.end_read_transaction(token) {
302 assert!(
303 transform.is_consistent(),
304 "observed a torn transform in a successful transaction: {transform:?}"
305 );
306 assert!(
307 params.is_consistent(),
308 "observed a torn parameter block in a successful transaction: {params:?}"
309 );
310 assert_eq!(
311 transform,
312 TestTransform::from_generation(params.values[0]),
313 "observed two payloads from different generations"
314 );
315
316 successful_transactions += 1;
317 observed = observed.max(params.values[0]);
318 }
319 }
320
321 shared.lock.read_transaction(|| {
322 shared.transform.read(&mut transform);
323 shared.params.read(&mut params);
324 });
325 assert!(transform.is_consistent());
326 assert!(params.is_consistent());
327
328 writer.join().unwrap();
329 assert!(successful_transactions > 0, "no read transaction ever succeeded");
330 }
331}