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> {
83 let file = OpenOptions::new()
84 .read(true).write(true).create(true).truncate(true)
85 .open(path)?;
86 file.set_len(WAKEUP_REGION_SIZE as u64)?;
87 let mut mmap = unsafe { MmapOptions::new().len(WAKEUP_REGION_SIZE).map_mut(&file)? };
88 let base = mmap.as_mut_ptr();
89 unsafe {
90 (base as *mut u64).write(WAKEUP_MAGIC);
91 (base.add(WAKEUP_OFFSET) as *mut AtomicU64).write(AtomicU64::new(0));
92 }
93 let ptr = unsafe { base.add(WAKEUP_OFFSET) as *const AtomicU64 };
94 Ok(Self { backing: WakeupBacking::File(file, mmap), ptr })
95 }
96
97 fn open_file(path: &Path) -> Result<Self, BlockingRWLockError> {
98 let file = OpenOptions::new().read(true).write(true).open(path)?;
99 if (file.metadata()?.len() as usize) < WAKEUP_REGION_SIZE {
100 return Err(BlockingRWLockError::LayoutMismatch);
101 }
102 let mut mmap = unsafe { MmapOptions::new().len(WAKEUP_REGION_SIZE).map_mut(&file)? };
103 let base = mmap.as_mut_ptr();
104 let magic = unsafe { (base as *const u64).read() };
105 if magic != WAKEUP_MAGIC {
106 return Err(BlockingRWLockError::LayoutMismatch);
107 }
108 let ptr = unsafe { base.add(WAKEUP_OFFSET) as *const AtomicU64 };
109 Ok(Self { backing: WakeupBacking::File(file, mmap), ptr })
110 }
111
112 #[inline]
113 fn atom(&self) -> &AtomicU64 { unsafe { &*self.ptr } }
114}
115
116pub struct BlockingRWLock {
118 inner: Arc<SharedRWLock>,
119 waker: Arc<CrossProcessWaker>,
120 wakeup: Arc<WakeupAtom>,
121}
122
123const PRE_PARK_SPIN: u32 = 32;
124
125impl BlockingRWLock {
126 pub fn create(base_path: impl AsRef<Path>) -> Result<Self, BlockingRWLockError> {
131 let base = base_path.as_ref();
132 let inner = SharedRWLock::create(rwlock_path(base))?;
133 let waker = CrossProcessWaker::create(waker_path(base), MAX_WAITERS_DEFAULT)?;
134 let wakeup = WakeupAtom::create_file(&wakeup_path(base))?;
135 Ok(Self {
136 inner: Arc::new(inner),
137 waker: Arc::new(waker),
138 wakeup: Arc::new(wakeup),
139 })
140 }
141
142 pub fn open(base_path: impl AsRef<Path>) -> Result<Self, BlockingRWLockError> {
144 let base = base_path.as_ref();
145 let inner = SharedRWLock::open(rwlock_path(base))?;
146 let waker = CrossProcessWaker::open(waker_path(base), MAX_WAITERS_DEFAULT)?;
147 let wakeup = WakeupAtom::open_file(&wakeup_path(base))?;
148 Ok(Self {
149 inner: Arc::new(inner),
150 waker: Arc::new(waker),
151 wakeup: Arc::new(wakeup),
152 })
153 }
154
155 pub fn try_read_lock(&self) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
157 match self.inner.try_read_lock() {
158 Ok(g) => {
159 std::mem::forget(g);
160 Ok(BlockingReadGuard { lock: self })
161 }
162 Err(e) => Err(BlockingRWLockError::Lock(e)),
163 }
164 }
165
166 pub fn try_write_lock(&self) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
168 match self.inner.try_write_lock() {
169 Ok(g) => {
170 std::mem::forget(g);
171 Ok(BlockingWriteGuard { lock: self })
172 }
173 Err(e) => Err(BlockingRWLockError::Lock(e)),
174 }
175 }
176
177 pub fn read_park(&self) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
179 loop {
180 if let Ok(g) = self.inner.try_read_lock() {
181 std::mem::forget(g);
182 return Ok(BlockingReadGuard { lock: self });
183 }
184 for _ in 0..PRE_PARK_SPIN {
185 if let Ok(g) = self.inner.try_read_lock() {
186 std::mem::forget(g);
187 return Ok(BlockingReadGuard { lock: self });
188 }
189 std::hint::spin_loop();
190 }
191 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
192 let token = self.waker.try_park(snapshot + 1)?;
193 if let Ok(g) = self.inner.try_read_lock() {
194 self.waker.release(token);
195 std::mem::forget(g);
196 return Ok(BlockingReadGuard { lock: self });
197 }
198 self.waker.wait(token, None)?;
199 }
200 }
201
202 pub fn read_park_timeout(
204 &self,
205 timeout: Duration,
206 ) -> Result<BlockingReadGuard<'_>, BlockingRWLockError> {
207 let deadline = Instant::now() + timeout;
208 loop {
209 if let Ok(g) = self.inner.try_read_lock() {
210 std::mem::forget(g);
211 return Ok(BlockingReadGuard { lock: self });
212 }
213 for _ in 0..PRE_PARK_SPIN {
214 if let Ok(g) = self.inner.try_read_lock() {
215 std::mem::forget(g);
216 return Ok(BlockingReadGuard { lock: self });
217 }
218 std::hint::spin_loop();
219 }
220 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
221 let token = self.waker.try_park(snapshot + 1)?;
222 if let Ok(g) = self.inner.try_read_lock() {
223 self.waker.release(token);
224 std::mem::forget(g);
225 return Ok(BlockingReadGuard { lock: self });
226 }
227 let now = Instant::now();
228 if now >= deadline {
229 self.waker.release(token);
230 return Err(BlockingRWLockError::Timeout);
231 }
232 let remaining = deadline - now;
233 match self.waker.wait(token, Some(remaining)) {
234 Ok(()) => continue,
235 Err(WakerError::Timeout) => return Err(BlockingRWLockError::Timeout),
236 Err(e) => return Err(BlockingRWLockError::Waker(e)),
237 }
238 }
239 }
240
241 pub fn write_park(&self) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
243 loop {
244 if let Ok(g) = self.inner.try_write_lock() {
245 std::mem::forget(g);
246 return Ok(BlockingWriteGuard { lock: self });
247 }
248 for _ in 0..PRE_PARK_SPIN {
249 if let Ok(g) = self.inner.try_write_lock() {
250 std::mem::forget(g);
251 return Ok(BlockingWriteGuard { lock: self });
252 }
253 std::hint::spin_loop();
254 }
255 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
256 let token = self.waker.try_park(snapshot + 1)?;
257 if let Ok(g) = self.inner.try_write_lock() {
258 self.waker.release(token);
259 std::mem::forget(g);
260 return Ok(BlockingWriteGuard { lock: self });
261 }
262 self.waker.wait(token, None)?;
263 }
264 }
265
266 pub fn write_park_timeout(
268 &self,
269 timeout: Duration,
270 ) -> Result<BlockingWriteGuard<'_>, BlockingRWLockError> {
271 let deadline = Instant::now() + timeout;
272 loop {
273 if let Ok(g) = self.inner.try_write_lock() {
274 std::mem::forget(g);
275 return Ok(BlockingWriteGuard { lock: self });
276 }
277 for _ in 0..PRE_PARK_SPIN {
278 if let Ok(g) = self.inner.try_write_lock() {
279 std::mem::forget(g);
280 return Ok(BlockingWriteGuard { lock: self });
281 }
282 std::hint::spin_loop();
283 }
284 let snapshot = self.wakeup.atom().load(Ordering::Acquire);
285 let token = self.waker.try_park(snapshot + 1)?;
286 if let Ok(g) = self.inner.try_write_lock() {
287 self.waker.release(token);
288 std::mem::forget(g);
289 return Ok(BlockingWriteGuard { lock: self });
290 }
291 let now = Instant::now();
292 if now >= deadline {
293 self.waker.release(token);
294 return Err(BlockingRWLockError::Timeout);
295 }
296 let remaining = deadline - now;
297 match self.waker.wait(token, Some(remaining)) {
298 Ok(()) => continue,
299 Err(WakerError::Timeout) => return Err(BlockingRWLockError::Timeout),
300 Err(e) => return Err(BlockingRWLockError::Waker(e)),
301 }
302 }
303 }
304
305 fn signal_unlock(&self) {
308 let new_gen = self.wakeup.atom().fetch_add(1, Ordering::Release) + 1;
309 self.waker.wake_up_to(new_gen);
310 }
311
312 pub fn inner(&self) -> &Arc<SharedRWLock> { &self.inner }
314}
315
316pub struct BlockingReadGuard<'a> {
318 lock: &'a BlockingRWLock,
319}
320
321impl Drop for BlockingReadGuard<'_> {
322 fn drop(&mut self) {
323 release_read_state(&self.lock.inner);
327 self.lock.signal_unlock();
328 }
329}
330
331pub struct BlockingWriteGuard<'a> {
333 lock: &'a BlockingRWLock,
334}
335
336impl Drop for BlockingWriteGuard<'_> {
337 fn drop(&mut self) {
338 release_write_state(&self.lock.inner);
339 self.lock.signal_unlock();
340 }
341}
342
343fn release_read_state(lock: &SharedRWLock) {
347 lock.release_read_for_blocking();
348}
349
350fn release_write_state(lock: &SharedRWLock) {
351 lock.release_write_for_blocking();
352}
353
354fn rwlock_path(base: &Path) -> PathBuf {
355 let mut p = base.as_os_str().to_owned();
356 p.push(".rwlock.bin");
357 PathBuf::from(p)
358}
359fn waker_path(base: &Path) -> PathBuf {
360 let mut p = base.as_os_str().to_owned();
361 p.push(".waker.bin");
362 PathBuf::from(p)
363}
364fn wakeup_path(base: &Path) -> PathBuf {
365 let mut p = base.as_os_str().to_owned();
366 p.push(".wakeup.bin");
367 PathBuf::from(p)
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use std::sync::atomic::AtomicU64;
374 use std::thread;
375
376 fn fresh_base() -> PathBuf {
377 let dir = std::env::temp_dir();
378 static N: AtomicU64 = AtomicU64::new(0);
379 let n = N.fetch_add(1, Ordering::Relaxed);
380 dir.join(format!("subetha_brwlock_test_{}_{}", std::process::id(), n))
381 }
382
383 fn cleanup(base: &Path) {
384 for suffix in [".rwlock.bin", ".waker.bin", ".wakeup.bin"] {
385 let mut p = base.as_os_str().to_owned();
386 p.push(suffix);
387 drop(std::fs::remove_file(PathBuf::from(p)));
388 }
389 }
390
391 #[test]
392 fn try_read_then_drop_then_try_write_succeeds() {
393 let base = fresh_base();
394 cleanup(&base);
395 let lock = BlockingRWLock::create(&base).expect("create");
396 let r = lock.try_read_lock().expect("read");
397 drop(r);
398 let w = lock.try_write_lock().expect("write");
399 drop(w);
400 cleanup(&base);
401 }
402
403 #[test]
404 fn write_park_completes_after_reader_releases() {
405 let base = fresh_base();
406 cleanup(&base);
407 let lock = Arc::new(BlockingRWLock::create(&base).expect("create"));
408 let r = lock.try_read_lock().expect("read");
409
410 let lock2 = Arc::clone(&lock);
416 let t = thread::spawn(move || {
417 let _w = lock2.write_park().expect("park-write");
418 Instant::now()
419 });
420 thread::sleep(Duration::from_millis(40));
421 let released_at = Instant::now();
422 drop(r);
423 let completed_at = t.join().unwrap();
424 assert!(completed_at >= released_at,
425 "write_park must not complete before the reader released");
426 cleanup(&base);
427 }
428
429 #[test]
430 fn read_park_timeout_returns_timeout_when_writer_held() {
431 let base = fresh_base();
432 cleanup(&base);
433 let lock = BlockingRWLock::create(&base).expect("create");
434 let _w = lock.try_write_lock().expect("hold-write");
435 let t0 = Instant::now();
436 let err = lock.read_park_timeout(Duration::from_millis(60));
437 assert!(matches!(err, Err(BlockingRWLockError::Timeout)));
438 assert!(t0.elapsed() >= Duration::from_millis(50));
439 cleanup(&base);
440 }
441}