1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Cross-platform synchronization primitives.
//!
//! This module provides a unified API for synchronization primitives that works
//! across both native and WASM targets:
//!
//! - On native platforms with `parking_lot` feature: Uses `parking_lot::RwLock`
//! for better performance (no poisoning, smaller size, spin-wait optimization)
//! - On WASM or without `parking_lot`: Falls back to `std::sync::RwLock`
//!
//! # Usage
//!
//! ```text
//! use crate::sync_compat::RwLock;
//!
//! let lock = RwLock::new(42);
//! let value = lock.read(); // Works on both parking_lot and std::sync
//! ```
// ============================================================================
// parking_lot backend (native + feature enabled)
// ============================================================================
#[cfg(all(feature = "parking_lot", not(target_arch = "wasm32")))]
pub use parking_lot::RwLock;
#[cfg(all(feature = "parking_lot", not(target_arch = "wasm32")))]
pub use parking_lot::RwLockReadGuard;
#[cfg(all(feature = "parking_lot", not(target_arch = "wasm32")))]
pub use parking_lot::RwLockWriteGuard;
// ============================================================================
// std::sync backend (WASM or parking_lot disabled)
// ============================================================================
/// A wrapper around `std::sync::RwLock` that provides a non-poisoning API
/// matching `parking_lot::RwLock`.
#[cfg(any(not(feature = "parking_lot"), target_arch = "wasm32"))]
#[derive(Debug, Default)]
pub struct RwLock<T>(std::sync::RwLock<T>);
#[cfg(any(not(feature = "parking_lot"), target_arch = "wasm32"))]
impl<T> RwLock<T> {
/// Creates a new RwLock.
#[inline]
pub const fn new(value: T) -> Self {
RwLock(std::sync::RwLock::new(value))
}
/// Acquires a read lock, panicking if the lock is poisoned.
#[inline]
pub fn read(&self) -> std::sync::RwLockReadGuard<'_, T> {
self.0.read().expect("RwLock poisoned")
}
/// Acquires a write lock, panicking if the lock is poisoned.
#[inline]
pub fn write(&self) -> std::sync::RwLockWriteGuard<'_, T> {
self.0.write().expect("RwLock poisoned")
}
/// Try to acquire a read lock without blocking.
///
/// Returns `Some(guard)` if successful, `None` if a writer holds the
/// lock. Matches `parking_lot::RwLock::try_read`'s shape. The
/// underlying `std::sync::RwLock::try_read` returns
/// `Err(TryLockError::WouldBlock)` if the lock is contended; we collapse
/// both poison and contention to `None` so the two backends are
/// API-compatible.
#[inline]
pub fn try_read(&self) -> Option<std::sync::RwLockReadGuard<'_, T>> {
self.0.try_read().ok()
}
/// Try to acquire a write lock without blocking.
///
/// Returns `Some(guard)` if successful, `None` if any reader or writer
/// holds the lock. Matches `parking_lot::RwLock::try_write`'s shape.
#[inline]
pub fn try_write(&self) -> Option<std::sync::RwLockWriteGuard<'_, T>> {
self.0.try_write().ok()
}
/// Returns a mutable reference to the underlying data.
#[inline]
pub fn get_mut(&mut self) -> &mut T {
self.0.get_mut().expect("RwLock poisoned")
}
/// Consumes the lock and returns the underlying data.
#[inline]
pub fn into_inner(self) -> T {
self.0.into_inner().expect("RwLock poisoned")
}
}
#[cfg(any(not(feature = "parking_lot"), target_arch = "wasm32"))]
pub use std::sync::RwLockReadGuard;
#[cfg(any(not(feature = "parking_lot"), target_arch = "wasm32"))]
pub use std::sync::RwLockWriteGuard;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rwlock_read() {
let lock = RwLock::new(42);
let value = lock.read();
assert_eq!(*value, 42);
}
#[test]
fn test_rwlock_write() {
let lock = RwLock::new(42);
{
let mut value = lock.write();
*value = 100;
}
let value = lock.read();
assert_eq!(*value, 100);
}
#[test]
fn test_rwlock_multiple_readers() {
let lock = RwLock::new(42);
let r1 = lock.read();
// Note: Can't get multiple read guards simultaneously in single-threaded test
// because std::sync::RwLock doesn't support that in this pattern
assert_eq!(*r1, 42);
}
#[test]
fn test_rwlock_get_mut() {
let mut lock = RwLock::new(42);
*lock.get_mut() = 100;
assert_eq!(*lock.read(), 100);
}
#[test]
fn test_rwlock_into_inner() {
let lock = RwLock::new(42);
assert_eq!(lock.into_inner(), 42);
}
#[test]
fn test_rwlock_try_read_succeeds_when_unlocked() {
let lock = RwLock::new(42);
let guard = lock
.try_read()
.expect("uncontended try_read should succeed");
assert_eq!(*guard, 42);
}
#[test]
fn test_rwlock_try_write_succeeds_when_unlocked() {
let lock = RwLock::new(42);
{
let mut guard = lock
.try_write()
.expect("uncontended try_write should succeed");
*guard = 100;
}
assert_eq!(*lock.read(), 100);
}
}