1use shared_memory::{Shmem, ShmemConf};
9use std::{
10 mem::size_of,
11 ops::{Deref, DerefMut},
12 sync::atomic::{AtomicU32, AtomicU64, Ordering},
13};
14use thiserror::Error;
15
16const MAGIC: u64 = 0x54414E47_4F434D50;
18
19const VERSION: u32 = 4;
21
22const DONE_BIT: u64 = 1 << 63;
24
25const STOP_FLAG: u32 = 1;
27
28#[repr(C)]
39struct CommpageLayout {
40 magic: u64,
41 version: u32,
42 flags: AtomicU32,
43 cursor_c: CacheLineAligned<Cursor>,
45 cursor_b: CacheLineAligned<Cursor>,
46}
47
48#[repr(C)]
49struct Cursor(AtomicU64);
50
51#[repr(C, align(64))]
61struct CacheLineAligned<T>(T);
62
63impl<T> Deref for CacheLineAligned<T> {
64 type Target = T;
65
66 fn deref(&self) -> &Self::Target {
67 &self.0
68 }
69}
70
71impl<T> DerefMut for CacheLineAligned<T> {
72 fn deref_mut(&mut self) -> &mut Self::Target {
73 &mut self.0
74 }
75}
76
77impl Cursor {
78 fn set_value(&mut self, value: u64) {
79 self.0.store(value & !DONE_BIT, Ordering::Release);
80 }
81
82 fn load(&self) -> (bool, u64) {
83 let val = self.0.load(Ordering::Acquire);
84 (val & DONE_BIT != 0, val & !DONE_BIT)
85 }
86
87 fn set_done(&mut self) {
88 self.0.fetch_or(DONE_BIT, Ordering::AcqRel);
89 }
90
91 fn reset(&mut self) {
92 self.0.store(0, Ordering::Release);
93 }
94}
95
96impl CommpageLayout {
97 fn init(&mut self) {
98 self.magic = MAGIC;
99 self.version = VERSION;
100 *self.flags.get_mut() = 0;
101 self.cursor_c.reset();
102 self.cursor_b.reset();
103 }
104
105 fn validate(&self) -> Result<(), CommpageError> {
106 if self.magic != MAGIC {
107 return Err(CommpageError::BadMagic);
108 }
109 if self.version != VERSION {
110 return Err(CommpageError::BadVersion {
111 got: self.version,
112 expected: VERSION,
113 });
114 }
115 Ok(())
116 }
117
118 fn cursor(&self, role: Role) -> &Cursor {
119 match role {
120 Role::Candidate => &self.cursor_c,
121 Role::Baseline => &self.cursor_b,
122 }
123 }
124
125 fn cursor_mut(&mut self, role: Role) -> &mut Cursor {
126 match role {
127 Role::Candidate => &mut self.cursor_c,
128 Role::Baseline => &mut self.cursor_b,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
135#[serde(rename_all = "lowercase")]
136pub enum Role {
137 Candidate,
138 Baseline,
139}
140
141impl Role {
142 pub fn peer(self) -> Self {
143 match self {
144 Self::Candidate => Self::Baseline,
145 Self::Baseline => Self::Candidate,
146 }
147 }
148}
149
150pub struct Commpage {
152 shmem: Shmem,
153}
154
155impl Commpage {
156 pub fn create() -> Result<Self, CommpageError> {
158 let shmem = ShmemConf::new()
159 .size(size_of::<CommpageLayout>())
160 .create()
161 .map_err(CommpageError::Shmem)?;
162
163 let layout = unsafe { &mut *(shmem.as_ptr() as *mut CommpageLayout) };
164 layout.init();
165
166 Ok(Commpage { shmem })
167 }
168
169 pub fn open(name: &str) -> Result<Self, CommpageError> {
171 let shmem = ShmemConf::new()
172 .os_id(name)
173 .open()
174 .map_err(CommpageError::Shmem)?;
175
176 let layout = unsafe { &*(shmem.as_ptr() as *const CommpageLayout) };
177 layout.validate()?;
178
179 Ok(Commpage { shmem })
180 }
181
182 pub fn os_id(&self) -> &str {
184 self.shmem.get_os_id()
185 }
186
187 fn layout(&self) -> &CommpageLayout {
188 unsafe { &*(self.shmem.as_ptr() as *const CommpageLayout) }
189 }
190
191 fn layout_mut(&mut self) -> &mut CommpageLayout {
192 unsafe { &mut *(self.shmem.as_ptr() as *mut CommpageLayout) }
193 }
194
195 pub fn set_cursor_value(&mut self, role: Role, sample_no: u64) {
197 self.layout_mut().cursor_mut(role).set_value(sample_no);
198 }
199
200 pub fn wait_for_cursor_value(&self, role: Role, target: u64) -> bool {
204 let cursor = self.layout().cursor(role);
205 loop {
206 let (stop, val) = cursor.load();
207 if stop {
208 return false;
209 }
210 if val >= target {
211 return true;
212 } else {
213 std::hint::spin_loop();
214 }
215 }
216 }
217
218 pub fn mark_done(&mut self, role: Role) {
220 self.layout_mut().cursor_mut(role).set_done();
221 }
222
223 pub fn is_done(&self, role: Role) -> bool {
225 let (done, _) = self.layout().cursor(role).load();
226 done
227 }
228
229 pub fn is_some_done(&self) -> bool {
231 self.is_done(Role::Candidate) || self.is_done(Role::Baseline)
232 }
233
234 pub fn load_cursor_value(&self, role: Role) -> usize {
238 let (_, value) = self.layout().cursor(role).load();
239 value as usize
240 }
241
242 pub fn set_stop(&mut self) {
244 self.layout().flags.fetch_or(STOP_FLAG, Ordering::Release);
245 }
246
247 pub fn is_stopped(&self) -> bool {
249 self.layout().flags.load(Ordering::Acquire) & STOP_FLAG != 0
250 }
251
252 pub fn reset(&mut self) {
254 self.layout_mut().flags.store(0, Ordering::Release);
255 self.layout_mut().cursor_c.reset();
256 self.layout_mut().cursor_b.reset();
257 }
258}
259
260#[derive(Debug, Error)]
261pub enum CommpageError {
262 #[error("shared memory error: {0}")]
263 Shmem(shared_memory::ShmemError),
264
265 #[error("invalid commpage magic number")]
266 BadMagic,
267
268 #[error("unsupported commpage version: {got} (expected {expected})")]
269 BadVersion { got: u32, expected: u32 },
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use std::thread;
276
277 #[test]
278 fn create_and_open() {
279 let cp = Commpage::create().unwrap();
280 let id = cp.os_id().to_string();
281 let cp2 = Commpage::open(&id).unwrap();
282 assert!(!cp2.is_stopped());
283 }
284
285 #[test]
286 fn stop_flag() {
287 let mut cp = Commpage::create().unwrap();
288 assert!(!cp.is_stopped());
289 cp.set_stop();
290 assert!(cp.is_stopped());
291 }
292
293 #[test]
294 fn done_bit() {
295 let mut cp = Commpage::create().unwrap();
296 assert!(!cp.is_done(Role::Candidate));
297 cp.set_cursor_value(Role::Candidate, 1);
298 cp.mark_done(Role::Candidate);
299
300 assert!(cp.is_done(Role::Candidate));
301 assert_eq!(cp.load_cursor_value(Role::Candidate), 1);
302 }
303
304 #[test]
305 fn peer_synchronization_two_threads() {
306 let mut cp = Commpage::create().unwrap();
307 let id = cp.os_id().to_string();
308
309 let num_samples = 64u64;
310
311 let handle = thread::spawn(move || {
312 let mut cp = Commpage::open(&id).unwrap();
313
314 for i in 0..num_samples {
315 cp.set_cursor_value(Role::Candidate, i);
316 cp.wait_for_cursor_value(Role::Baseline, i);
317 }
318 cp.mark_done(Role::Candidate);
319 });
320
321 for i in 0..num_samples {
322 cp.set_cursor_value(Role::Baseline, i);
323 cp.wait_for_cursor_value(Role::Candidate, i);
324 }
325 cp.mark_done(Role::Baseline);
326
327 handle.join().unwrap();
328
329 assert_eq!(
330 cp.load_cursor_value(Role::Baseline),
331 (num_samples - 1) as usize
332 );
333 assert_eq!(
334 cp.load_cursor_value(Role::Candidate),
335 (num_samples - 1) as usize
336 );
337 }
338}