1use std::fs::{File, OpenOptions};
17use std::mem::size_of;
18use std::path::Path;
19use std::sync::atomic::{AtomicU64, Ordering};
20
21use memmap2::{MmapMut, MmapOptions};
22
23pub const RWLOCK_MAGIC: u64 = 0x4150_5257_4C4F_434B;
24
25const CREATE_RACE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
29
30const WRITER_BIT: u64 = 1u64 << 63;
31const WAITING_SHIFT: u64 = 32;
32const WAITING_MASK: u64 = 0x7FFF_FFFF << WAITING_SHIFT;
33const READERS_MASK: u64 = 0xFFFF_FFFF;
34
35#[repr(C, align(64))]
36pub struct RWLockHeader {
37 pub magic: u64,
38 pub state: AtomicU64,
39 _pad: [u8; 48],
40}
41
42const _: () = {
43 assert!(size_of::<RWLockHeader>() == 64);
44};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum RWLockError {
48 WouldBlock,
49 LayoutMismatch,
50 IoError(std::io::ErrorKind),
51}
52
53impl From<std::io::Error> for RWLockError {
54 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
55}
56
57pub struct SharedRWLock {
58 _file: File,
59 mmap: MmapMut,
60 header_sidecar: subetha_core::HandshakeHeader,
61 ring_sidecar: Box<subetha_core::ObservationRing>,
62}
63
64unsafe impl Send for SharedRWLock {}
65unsafe impl Sync for SharedRWLock {}
66
67impl subetha_sidecar::AdaptiveInstance for SharedRWLock {
68 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
69 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
70 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
71 Box::new(subetha_sidecar::NoMigrationPolicy)
72 }
73}
74
75impl SharedRWLock {
76 pub fn create(path: impl AsRef<Path>) -> Result<Self, RWLockError> {
80 Self::create_or_open(path)
81 }
82
83 pub fn reset(path: impl AsRef<Path>) -> Result<Self, RWLockError> {
87 let total = size_of::<RWLockHeader>();
88 let file = OpenOptions::new()
89 .read(true).write(true).create(true).truncate(true)
90 .open(path.as_ref())?;
91 file.set_len(total as u64)?;
92 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
93 let hdr = mmap.as_mut_ptr() as *mut RWLockHeader;
94 unsafe {
95 std::ptr::write_bytes(hdr as *mut u8, 0, total);
96 (*hdr).magic = RWLOCK_MAGIC;
97 }
98 Ok(Self {
99 _file: file, mmap,
100 header_sidecar: subetha_core::HandshakeHeader::new(),
101 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
102 })
103 }
104
105 pub fn create_or_open(path: impl AsRef<Path>) -> Result<Self, RWLockError> {
118 let total = size_of::<RWLockHeader>();
119 match OpenOptions::new()
120 .read(true)
121 .write(true)
122 .create_new(true)
123 .open(path.as_ref())
124 {
125 Ok(file) => {
126 file.set_len(total as u64)?;
127 let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
128 let hdr = mmap.as_mut_ptr() as *mut RWLockHeader;
129 unsafe {
130 std::ptr::write_bytes(hdr as *mut u8, 0, total);
131 (*hdr).magic = RWLOCK_MAGIC;
134 }
135 Ok(Self {
136 _file: file,
137 mmap,
138 header_sidecar: subetha_core::HandshakeHeader::new(),
139 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
140 })
141 }
142 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
143 let deadline = std::time::Instant::now() + CREATE_RACE_TIMEOUT;
146 loop {
147 match Self::open(path.as_ref()) {
148 Ok(l) => return Ok(l),
149 Err(RWLockError::LayoutMismatch) | Err(RWLockError::IoError(_))
150 if std::time::Instant::now() < deadline =>
151 {
152 std::thread::yield_now();
153 }
154 Err(other) => return Err(other),
155 }
156 }
157 }
158 Err(e) => Err(e.into()),
159 }
160 }
161
162 pub fn open(path: impl AsRef<Path>) -> Result<Self, RWLockError> {
163 let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
164 if file.metadata()?.len() < size_of::<RWLockHeader>() as u64 {
165 return Err(RWLockError::LayoutMismatch);
166 }
167 let mmap = unsafe {
168 MmapOptions::new().len(size_of::<RWLockHeader>()).map_mut(&file)?
169 };
170 let hdr = unsafe { &*(mmap.as_ptr() as *const RWLockHeader) };
171 if hdr.magic != RWLOCK_MAGIC {
172 return Err(RWLockError::LayoutMismatch);
173 }
174 Ok(Self {
175 _file: file, mmap,
176 header_sidecar: subetha_core::HandshakeHeader::new(),
177 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
178 })
179 }
180
181 fn state(&self) -> &AtomicU64 {
182 unsafe { &(*(self.mmap.as_ptr() as *const RWLockHeader)).state }
183 }
184
185 pub fn try_read_lock(&self) -> Result<ReadGuard<'_>, RWLockError> {
187 let r = self.try_read_lock_inner();
188 self.ring_sidecar.push_op(
189 crate::sidecar_ops::rw_lock::OP_TRY_READ,
190 if r.is_err() { 1 } else { 0 },
191 );
192 r
193 }
194
195 fn try_read_lock_inner(&self) -> Result<ReadGuard<'_>, RWLockError> {
196 loop {
197 let s = self.state().load(Ordering::Acquire);
198 let writer_active = (s & WRITER_BIT) != 0;
199 let writers_waiting = (s & WAITING_MASK) >> WAITING_SHIFT;
200 if writer_active || writers_waiting > 0 {
201 return Err(RWLockError::WouldBlock);
202 }
203 let readers = s & READERS_MASK;
204 let new = (s & !READERS_MASK) | (readers + 1);
205 if self.state().compare_exchange(
206 s, new, Ordering::AcqRel, Ordering::Acquire,
207 ).is_ok() {
208 return Ok(ReadGuard { lock: self });
209 }
210 }
211 }
212
213 pub fn read_lock(&self) -> ReadGuard<'_> {
216 let mut spins = 0u32;
217 loop {
218 if let Ok(g) = self.try_read_lock_inner() {
219 self.ring_sidecar.push_op(
220 crate::sidecar_ops::rw_lock::OP_READ,
221 if spins > 0 { 1 } else { 0 }, );
223 return g;
224 }
225 spins += 1;
226 if spins < 32 {
227 std::hint::spin_loop();
228 } else if spins < 256 {
229 std::thread::yield_now();
230 } else {
231 std::thread::sleep(std::time::Duration::from_micros(50));
232 }
233 }
234 }
235
236 pub fn try_write_lock(&self) -> Result<WriteGuard<'_>, RWLockError> {
238 let r = self.try_write_lock_inner();
239 self.ring_sidecar.push_op(
240 crate::sidecar_ops::rw_lock::OP_TRY_WRITE,
241 if r.is_err() { 1 } else { 0 },
242 );
243 r
244 }
245
246 fn try_write_lock_inner(&self) -> Result<WriteGuard<'_>, RWLockError> {
247 loop {
248 let s = self.state().load(Ordering::Acquire);
249 let writer_active = (s & WRITER_BIT) != 0;
250 let readers = s & READERS_MASK;
251 if writer_active || readers > 0 {
252 return Err(RWLockError::WouldBlock);
253 }
254 let new = (s & !WRITER_BIT) | WRITER_BIT;
255 if self.state().compare_exchange(
256 s, new, Ordering::AcqRel, Ordering::Acquire,
257 ).is_ok() {
258 return Ok(WriteGuard { lock: self });
259 }
260 }
261 }
262
263 pub fn write_lock(&self) -> WriteGuard<'_> {
266 self.state().fetch_add(1u64 << WAITING_SHIFT, Ordering::AcqRel);
268 let mut spins = 0u32;
269 loop {
270 let s = self.state().load(Ordering::Acquire);
271 let writer_active = (s & WRITER_BIT) != 0;
272 let readers = s & READERS_MASK;
273 if !writer_active && readers == 0 {
274 let new = (s & READERS_MASK) | WRITER_BIT
276 | ((((s & WAITING_MASK) >> WAITING_SHIFT) - 1) << WAITING_SHIFT);
277 if self.state().compare_exchange(
278 s, new, Ordering::AcqRel, Ordering::Acquire,
279 ).is_ok() {
280 self.ring_sidecar.push_op(
281 crate::sidecar_ops::rw_lock::OP_WRITE,
282 if spins > 0 { 1 } else { 0 }, );
284 return WriteGuard { lock: self };
285 }
286 }
287 spins += 1;
288 if spins < 32 {
289 std::hint::spin_loop();
290 } else if spins < 256 {
291 std::thread::yield_now();
292 } else {
293 std::thread::sleep(std::time::Duration::from_micros(50));
294 }
295 }
296 }
297
298 pub fn reader_count(&self) -> u32 {
300 (self.state().load(Ordering::Acquire) & READERS_MASK) as u32
301 }
302
303 pub fn has_writer(&self) -> bool {
305 (self.state().load(Ordering::Acquire) & WRITER_BIT) != 0
306 }
307
308 pub fn waiting_writers(&self) -> u32 {
310 ((self.state().load(Ordering::Acquire) & WAITING_MASK) >> WAITING_SHIFT) as u32
311 }
312
313 fn release_read(&self) {
319 loop {
320 let s = self.state().load(Ordering::Acquire);
321 let readers = s & READERS_MASK;
322 debug_assert!(
323 readers > 0,
324 "SharedRWLock::release_read called when reader count is 0 - \
325 indicates a protocol violation (double-release or release \
326 without acquire). The lock counter will not be decremented.",
327 );
328 if readers == 0 { return; }
329 let new = (s & !READERS_MASK) | (readers - 1);
330 if self.state().compare_exchange(
331 s, new, Ordering::AcqRel, Ordering::Acquire,
332 ).is_ok() {
333 return;
334 }
335 }
336 }
337
338 fn release_write(&self) {
342 let prev = self.state().fetch_and(!WRITER_BIT, Ordering::AcqRel);
343 debug_assert!(
344 (prev & WRITER_BIT) != 0,
345 "SharedRWLock::release_write called when no writer holds the lock - \
346 indicates a protocol violation (double-release or release without \
347 acquire).",
348 );
349 }
350
351 pub fn release_read_for_blocking(&self) { self.release_read(); }
357
358 pub fn release_write_for_blocking(&self) { self.release_write(); }
361
362 pub fn flush(&self) -> Result<(), RWLockError> {
363 self.mmap.flush()?;
364 Ok(())
365 }
366 pub fn flush_async(&self) -> Result<(), RWLockError> {
367 self.mmap.flush_async()?;
368 Ok(())
369 }
370}
371
372pub struct ReadGuard<'a> { lock: &'a SharedRWLock }
373impl Drop for ReadGuard<'_> {
374 fn drop(&mut self) { self.lock.release_read(); }
375}
376
377pub struct WriteGuard<'a> { lock: &'a SharedRWLock }
378impl Drop for WriteGuard<'_> {
379 fn drop(&mut self) { self.lock.release_write(); }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use std::sync::atomic::{AtomicU32, Ordering as O};
386 use std::sync::Arc;
387 use std::thread;
388
389 fn tmp(name: &str) -> std::path::PathBuf {
390 let mut p = std::env::temp_dir();
391 let pid = std::process::id();
392 p.push(format!("subetha-rwlock-{name}-{pid}.bin"));
393 p
394 }
395
396 #[test]
403 fn create_or_open_racing_callers_do_not_clear_a_held_writer() {
404 let p = tmp("race");
405 std::fs::remove_file(&p).ok();
406
407 let holder = SharedRWLock::create_or_open(&p).unwrap();
408 let guard = holder.try_write_lock().expect("uncontended write lock");
409 assert!(holder.has_writer());
410
411 let path = Arc::new(p.clone());
413 let cleared = Arc::new(AtomicU32::new(0));
414 let mut hs = Vec::new();
415 for _ in 0..8 {
416 let path = Arc::clone(&path);
417 let cleared = Arc::clone(&cleared);
418 hs.push(thread::spawn(move || {
419 let l = SharedRWLock::create_or_open(&*path).expect("open existing");
420 if !l.has_writer() {
421 cleared.fetch_add(1, O::Relaxed);
422 }
423 }));
424 }
425 for h in hs {
426 h.join().unwrap();
427 }
428
429 assert_eq!(
430 cleared.load(O::Relaxed),
431 0,
432 "a concurrent create_or_open zeroed a writer flag another holder owned",
433 );
434 assert!(holder.has_writer(), "the holder lost its own write lock");
435 drop(guard);
436 std::fs::remove_file(&p).ok();
437 }
438
439 #[test]
440 fn create_initial_state_is_idle() {
441 let p = tmp("init");
442 let l = SharedRWLock::create(&p).unwrap();
443 assert_eq!(l.reader_count(), 0);
444 assert!(!l.has_writer());
445 assert_eq!(l.waiting_writers(), 0);
446 std::fs::remove_file(&p).ok();
447 }
448
449 #[test]
450 fn try_read_succeeds_when_idle() {
451 let p = tmp("try-read");
452 let l = SharedRWLock::create(&p).unwrap();
453 let g = l.try_read_lock().unwrap();
454 assert_eq!(l.reader_count(), 1);
455 drop(g);
456 assert_eq!(l.reader_count(), 0);
457 std::fs::remove_file(&p).ok();
458 }
459
460 #[test]
461 fn try_write_succeeds_when_idle() {
462 let p = tmp("try-write");
463 let l = SharedRWLock::create(&p).unwrap();
464 let g = l.try_write_lock().unwrap();
465 assert!(l.has_writer());
466 drop(g);
467 assert!(!l.has_writer());
468 std::fs::remove_file(&p).ok();
469 }
470
471 #[test]
472 fn multiple_readers_coexist() {
473 let p = tmp("multi-read");
474 let l = SharedRWLock::create(&p).unwrap();
475 let g1 = l.try_read_lock().unwrap();
476 let g2 = l.try_read_lock().unwrap();
477 let g3 = l.try_read_lock().unwrap();
478 assert_eq!(l.reader_count(), 3);
479 drop(g1); drop(g2); drop(g3);
480 assert_eq!(l.reader_count(), 0);
481 std::fs::remove_file(&p).ok();
482 }
483
484 #[test]
485 fn writer_excludes_readers() {
486 let p = tmp("w-excl-r");
487 let l = SharedRWLock::create(&p).unwrap();
488 let _w = l.try_write_lock().unwrap();
489 assert_eq!(l.try_read_lock().err(), Some(RWLockError::WouldBlock));
490 std::fs::remove_file(&p).ok();
491 }
492
493 #[test]
494 fn reader_excludes_writer() {
495 let p = tmp("r-excl-w");
496 let l = SharedRWLock::create(&p).unwrap();
497 let _r = l.try_read_lock().unwrap();
498 assert_eq!(l.try_write_lock().err(), Some(RWLockError::WouldBlock));
499 std::fs::remove_file(&p).ok();
500 }
501
502 #[test]
503 fn writer_excludes_writer() {
504 let p = tmp("w-excl-w");
505 let l = SharedRWLock::create(&p).unwrap();
506 let _w = l.try_write_lock().unwrap();
507 assert_eq!(l.try_write_lock().err(), Some(RWLockError::WouldBlock));
508 std::fs::remove_file(&p).ok();
509 }
510
511 #[test]
512 fn write_lock_blocks_until_readers_drop() {
513 let p = tmp("w-blocks");
519 let l = Arc::new(SharedRWLock::create(&p).unwrap());
520 let l_reader = l.clone();
521 let reader_done = Arc::new(AtomicU32::new(0));
522 let reader_done_clone = reader_done.clone();
523 let reader = thread::spawn(move || {
524 let _g = l_reader.read_lock();
525 std::thread::sleep(std::time::Duration::from_millis(30));
526 reader_done_clone.store(1, O::Release);
527 });
529 let acquire_deadline = std::time::Instant::now()
532 + std::time::Duration::from_secs(5);
533 while l.reader_count() != 1
534 && std::time::Instant::now() < acquire_deadline
535 {
536 std::thread::yield_now();
537 }
538 assert_eq!(l.reader_count(), 1);
539
540 let l_writer = l.clone();
541 let writer_started = std::time::Instant::now();
542 let writer = thread::spawn(move || {
543 let _g = l_writer.write_lock();
544 writer_started.elapsed()
545 });
546
547 let elapsed = writer.join().unwrap();
548 reader.join().unwrap();
549 assert!(
552 elapsed >= std::time::Duration::from_millis(15),
553 "writer should have blocked for the reader's hold time, got {elapsed:?}",
554 );
555 assert_eq!(reader_done.load(O::Acquire), 1);
556 std::fs::remove_file(&p).ok();
557 }
558
559 #[test]
560 fn writer_priority_blocks_new_readers() {
561 let p = tmp("w-priority");
564 let l = SharedRWLock::create(&p).unwrap();
565 l.state().fetch_add(1u64 << WAITING_SHIFT, Ordering::AcqRel);
568 assert_eq!(l.try_read_lock().err(), Some(RWLockError::WouldBlock));
569 l.state().fetch_sub(1u64 << WAITING_SHIFT, Ordering::AcqRel);
571 std::fs::remove_file(&p).ok();
572 }
573
574 #[test]
575 fn concurrent_readers_all_succeed() {
576 let p = tmp("concurrent-r");
577 let l = Arc::new(SharedRWLock::create(&p).unwrap());
578 let n = 8;
579 let count = Arc::new(AtomicU32::new(0));
580 let mut handles = vec![];
581 for _ in 0..n {
582 let l = l.clone();
583 let count = count.clone();
584 handles.push(thread::spawn(move || {
585 let _g = l.read_lock();
586 count.fetch_add(1, O::AcqRel);
587 std::thread::sleep(std::time::Duration::from_millis(5));
588 }));
589 }
590 for h in handles { h.join().unwrap(); }
591 assert_eq!(count.load(O::Acquire), n);
592 std::fs::remove_file(&p).ok();
593 }
594
595 #[test]
596 fn cross_handle_visibility() {
597 let p = tmp("cross-handle");
598 let w = SharedRWLock::create(&p).unwrap();
599 let r = SharedRWLock::open(&p).unwrap();
600 let _g = w.try_read_lock().unwrap();
601 assert_eq!(r.reader_count(), 1);
603 assert_eq!(r.try_write_lock().err(), Some(RWLockError::WouldBlock));
604 std::fs::remove_file(&p).ok();
605 }
606
607 #[test]
608 fn writer_then_reader_serialized() {
609 let p = tmp("w-then-r");
610 let l = SharedRWLock::create(&p).unwrap();
611 {
612 let _w = l.try_write_lock().unwrap();
613 }
614 let _r = l.try_read_lock().unwrap();
616 std::fs::remove_file(&p).ok();
617 }
618}