subetha_cxc/
blocking_rw_lock.rs1use std::fs::OpenOptions;
23use std::path::{Path, PathBuf};
24use std::sync::Arc;
25use std::sync::atomic::{AtomicU64, Ordering};
26use std::time::{Duration, Instant};
27
28use memmap2::{MmapMut, MmapOptions};
29
30use crate::cross_process_waker::{
31 CrossProcessWaker, MAX_WAITERS_DEFAULT, WakerError,
32};
33use crate::shared_rw_lock::{RWLockError, SharedRWLock};
34
35const WAKEUP_MAGIC: u64 = 0x4257_524C_5742_4B30; const WAKEUP_REGION_SIZE: usize = 64;
37const WAKEUP_OFFSET: usize = 8;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum BlockingRWLockError {
42 Lock(RWLockError),
43 Waker(WakerError),
44 Timeout,
45 LayoutMismatch,
46 Io(std::io::ErrorKind),
47}
48
49impl From<RWLockError> for BlockingRWLockError {
50 fn from(e: RWLockError) -> Self { Self::Lock(e) }
51}
52impl From<WakerError> for BlockingRWLockError {
53 fn from(e: WakerError) -> Self {
54 match e {
55 WakerError::Timeout => Self::Timeout,
56 other => Self::Waker(other),
57 }
58 }
59}
60impl From<std::io::Error> for BlockingRWLockError {
61 fn from(e: std::io::Error) -> Self { Self::Io(e.kind()) }
62}
63
64#[allow(dead_code)]
65enum WakeupBacking {
66 Anon(MmapMut),
67 File(std::fs::File, MmapMut),
68}
69
70struct WakeupAtom {
71 #[allow(dead_code)]
72 backing: WakeupBacking,
73 ptr: *const AtomicU64,
74}
75
76unsafe impl Send for WakeupAtom {}
79unsafe impl Sync for WakeupAtom {}
80
81impl WakeupAtom {
82 fn create_file(path: &Path) -> Result<Self, BlockingRWLockError> {
86 let (file, mut mmap) = crate::mmf_attach::create_or_attach(
87 path,
88 WAKEUP_REGION_SIZE,
89 |base| unsafe {
90 (base.add(WAKEUP_OFFSET) as *mut AtomicU64).write(AtomicU64::new(0));
91 std::ptr::write_volatile(base as *mut u64, WAKEUP_MAGIC);
92 },
93 |base| unsafe { (base as *const u64).read() == WAKEUP_MAGIC },
94 )?;
95 let base = mmap.as_mut_ptr();
96 let ptr = unsafe { base.add(WAKEUP_OFFSET) as *const AtomicU64 };
97 Ok(Self { backing: WakeupBacking::File(file, mmap), ptr })
98 }
99
100 fn open_file(path: &Path) -> Result<Self, BlockingRWLockError> {
101 let file = OpenOptions::new().read(true).write(true).open(path)?;
102 if (file.metadata()?.len() as usize) < WAKEUP_REGION_SIZE {
103 return Err(BlockingRWLockError::LayoutMismatch);
104 }
105 let mut mmap = unsafe { MmapOptions::new().len(WAKEUP_REGION_SIZE).map_mut(&file)? };
106 let base = mmap.as_mut_ptr();
107 let magic = unsafe { (base as *const u64).read() };
108 if magic != WAKEUP_MAGIC {
109 return Err(BlockingRWLockError::LayoutMismatch);
110 }
111 let ptr = unsafe { base.add(WAKEUP_OFFSET) as *const AtomicU64 };
112 Ok(Self { backing: WakeupBacking::File(file, mmap), ptr })
113 }
114
115 #[inline]
116 fn atom(&self) -> &AtomicU64 { unsafe { &*self.ptr } }
117}
118
119pub struct BlockingRWLock {
121 inner: Arc<SharedRWLock>,
122 waker: Arc<CrossProcessWaker>,
123 wakeup: Arc<WakeupAtom>,
124}
125
126const PRE_PARK_SPIN: u32 = 32;
127
128impl BlockingRWLock {
129 pub fn create(base_path: impl AsRef<Path>) -> Result<Self, BlockingRWLockError> {
134 let base = base_path.as_ref();
135 let inner = SharedRWLock::create(rwlock_path(base))?;
136 let waker = CrossProcessWaker::create(waker_path(base), MAX_WAITERS_DEFAULT)?;
137 let wakeup = WakeupAtom::create_file(&wakeup_path(base))?;
138 Ok(Self {
139 inner: Arc::new(inner),
140 waker: Arc::new(waker),
141 wakeup: Arc::new(wakeup),
142 })
143 }
144
145 pub fn open(base_path: impl AsRef<Path>) -> Result<Self, BlockingRWLockError> {
147 let base = base_path.as_ref();
148 let inner = SharedRWLock::open(rwlock_path(base))?;
149 let waker = CrossProcessWaker::open(waker_path(base), MAX_WAITERS_DEFAULT)?;
150 let wakeup = WakeupAtom::open_file(&wakeup_path(base))?;
151 Ok(Self {
152 inner: Arc::new(inner),
153 waker: Arc::new(waker),
154 wakeup: Arc::new(wakeup),
155 })
156 }
157
158 pub fn try_read_lock(&self) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
160 match self.inner.try_read_lock() {
161 Ok(g) => {
162 std::mem::forget(g);
163 Ok(BlockingReadGuard { lock: self })
164 }
165 Err(e) => Err(BlockingRWLockError::Lock(e)),
166 }
167 }
168
169 pub fn try_write_lock(&self) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
171 match self.inner.try_write_lock() {
172 Ok(g) => {
173 std::mem::forget(g);
174 Ok(BlockingWriteGuard { lock: self })
175 }
176 Err(e) => Err(BlockingRWLockError::Lock(e)),
177 }
178 }
179
180 pub fn read_park(&self) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
182 loop {
183 if let Ok(g) = self.inner.try_read_lock() {
184 std::mem::forget(g);
185 return Ok(BlockingReadGuard { lock: self });
186 }
187 for _ in 0..PRE_PARK_SPIN {
188 if let Ok(g) = self.inner.try_read_lock() {
189 std::mem::forget(g);
190 return Ok(BlockingReadGuard { lock: self });
191 }
192 std::hint::spin_loop();
193 }
194 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
195 let token = self.waker.try_park(snapshot + 1)?;
196 if let Ok(g) = self.inner.try_read_lock() {
197 self.waker.release(token);
198 std::mem::forget(g);
199 return Ok(BlockingReadGuard { lock: self });
200 }
201 self.waker.wait(token, None)?;
202 }
203 }
204
205 pub fn read_park_timeout(
207 &self,
208 timeout: Duration,
209 ) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
210 let deadline = Instant::now() + timeout;
211 loop {
212 if let Ok(g) = self.inner.try_read_lock() {
213 std::mem::forget(g);
214 return Ok(BlockingReadGuard { lock: self });
215 }
216 for _ in 0..PRE_PARK_SPIN {
217 if let Ok(g) = self.inner.try_read_lock() {
218 std::mem::forget(g);
219 return Ok(BlockingReadGuard { lock: self });
220 }
221 std::hint::spin_loop();
222 }
223 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
224 let token = self.waker.try_park(snapshot + 1)?;
225 if let Ok(g) = self.inner.try_read_lock() {
226 self.waker.release(token);
227 std::mem::forget(g);
228 return Ok(BlockingReadGuard { lock: self });
229 }
230 let now = Instant::now();
231 if now >= deadline {
232 self.waker.release(token);
233 return Err(BlockingRWLockError::Timeout);
234 }
235 let remaining = deadline - now;
236 match self.waker.wait(token, Some(remaining)) {
237 Ok(()) => continue,
238 Err(WakerError::Timeout) => return Err(BlockingRWLockError::Timeout),
239 Err(e) => return Err(BlockingRWLockError::Waker(e)),
240 }
241 }
242 }
243
244 pub fn write_park(&self) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
246 loop {
247 if let Ok(g) = self.inner.try_write_lock() {
248 std::mem::forget(g);
249 return Ok(BlockingWriteGuard { lock: self });
250 }
251 for _ in 0..PRE_PARK_SPIN {
252 if let Ok(g) = self.inner.try_write_lock() {
253 std::mem::forget(g);
254 return Ok(BlockingWriteGuard { lock: self });
255 }
256 std::hint::spin_loop();
257 }
258 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
259 let token = self.waker.try_park(snapshot + 1)?;
260 if let Ok(g) = self.inner.try_write_lock() {
261 self.waker.release(token);
262 std::mem::forget(g);
263 return Ok(BlockingWriteGuard { lock: self });
264 }
265 self.waker.wait(token, None)?;
266 }
267 }
268
269 pub fn write_park_timeout(
271 &self,
272 timeout: Duration,
273 ) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
274 let deadline = Instant::now() + timeout;
275 loop {
276 if let Ok(g) = self.inner.try_write_lock() {
277 std::mem::forget(g);
278 return Ok(BlockingWriteGuard { lock: self });
279 }
280 for _ in 0..PRE_PARK_SPIN {
281 if let Ok(g) = self.inner.try_write_lock() {
282 std::mem::forget(g);
283 return Ok(BlockingWriteGuard { lock: self });
284 }
285 std::hint::spin_loop();
286 }
287 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
288 let token = self.waker.try_park(snapshot + 1)?;
289 if let Ok(g) = self.inner.try_write_lock() {
290 self.waker.release(token);
291 std::mem::forget(g);
292 return Ok(BlockingWriteGuard { lock: self });
293 }
294 let now = Instant::now();
295 if now >= deadline {
296 self.waker.release(token);
297 return Err(BlockingRWLockError::Timeout);
298 }
299 let remaining = deadline - now;
300 match self.waker.wait(token, Some(remaining)) {
301 Ok(()) => continue,
302 Err(WakerError::Timeout) => return Err(BlockingRWLockError::Timeout),
303 Err(e) => return Err(BlockingRWLockError::Waker(e)),
304 }
305 }
306 }
307
308 fn signal_unlock(&self) {
311 let new_gen = self.wakeup.atom().fetch_add(1, Ordering::Release) + 1;
312 self.waker.wake_up_to(new_gen);
313 }
314
315 pub fn inner(&self) -> &Arc<SharedRWLock> { &self.inner }
317}
318
319pub struct BlockingReadGuard<'a> {
321 lock: &'a BlockingRWLock,
322}
323
324impl Drop for BlockingReadGuard<'_> {
325 fn drop(&mut self) {
326 release_read_state(&self.lock.inner);
330 self.lock.signal_unlock();
331 }
332}
333
334pub struct BlockingWriteGuard<'a> {
336 lock: &'a BlockingRWLock,
337}
338
339impl Drop for BlockingWriteGuard<'_> {
340 fn drop(&mut self) {
341 release_write_state(&self.lock.inner);
342 self.lock.signal_unlock();
343 }
344}
345
346fn release_read_state(lock: &SharedRWLock) {
350 lock.release_read_for_blocking();
351}
352
353fn release_write_state(lock: &SharedRWLock) {
354 lock.release_write_for_blocking();
355}
356
357fn rwlock_path(base: &Path) -> PathBuf {
358 let mut p = base.as_os_str().to_owned();
359 p.push(".rwlock.bin");
360 PathBuf::from(p)
361}
362fn waker_path(base: &Path) -> PathBuf {
363 let mut p = base.as_os_str().to_owned();
364 p.push(".waker.bin");
365 PathBuf::from(p)
366}
367fn wakeup_path(base: &Path) -> PathBuf {
368 let mut p = base.as_os_str().to_owned();
369 p.push(".wakeup.bin");
370 PathBuf::from(p)
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use std::sync::atomic::AtomicU64;
377 use std::thread;
378
379 fn fresh_base() -> PathBuf {
380 let dir = std::env::temp_dir();
381 static N: AtomicU64 = AtomicU64::new(0);
382 let n = N.fetch_add(1, Ordering::Relaxed);
383 dir.join(format!("subetha_brwlock_test_{}_{}", std::process::id(), n))
384 }
385
386 fn cleanup(base: &Path) {
387 for suffix in [".rwlock.bin", ".waker.bin", ".wakeup.bin"] {
388 let mut p = base.as_os_str().to_owned();
389 p.push(suffix);
390 drop(std::fs::remove_file(PathBuf::from(p)));
391 }
392 }
393
394 #[test]
395 fn try_read_then_drop_then_try_write_succeeds() {
396 let base = fresh_base();
397 cleanup(&base);
398 let lock = BlockingRWLock::create(&base).expect("create");
399 let r = lock.try_read_lock().expect("read");
400 drop(r);
401 let w = lock.try_write_lock().expect("write");
402 drop(w);
403 cleanup(&base);
404 }
405
406 #[test]
407 fn write_park_completes_after_reader_releases() {
408 let base = fresh_base();
409 cleanup(&base);
410 let lock = Arc::new(BlockingRWLock::create(&base).expect("create"));
411 let r = lock.try_read_lock().expect("read");
412
413 let lock2 = Arc::clone(&lock);
419 let t = thread::spawn(move || {
420 let _w = lock2.write_park().expect("park-write");
421 Instant::now()
422 });
423 thread::sleep(Duration::from_millis(40));
424 let released_at = Instant::now();
425 drop(r);
426 let completed_at = t.join().unwrap();
427 assert!(completed_at >= released_at,
428 "write_park must not complete before the reader released");
429 cleanup(&base);
430 }
431
432 #[test]
433 fn read_park_timeout_returns_timeout_when_writer_held() {
434 let base = fresh_base();
435 cleanup(&base);
436 let lock = BlockingRWLock::create(&base).expect("create");
437 let _w = lock.try_write_lock().expect("hold-write");
438 let t0 = Instant::now();
439 let err = lock.read_park_timeout(Duration::from_millis(60));
440 assert!(matches!(err, Err(BlockingRWLockError::Timeout)));
441 assert!(t0.elapsed() >= Duration::from_millis(50));
442 cleanup(&base);
443 }
444}