use std::sync::{RwLock, Mutex, PoisonError, RwLockReadGuard, RwLockWriteGuard, MutexGuard};
use std::fmt;
#[cfg(feature = "pyo3")]
use pyo3::pyclass;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "pyo3", pyclass)]
pub struct RiLockError {
context: String,
is_poisoned: bool,
}
impl RiLockError {
pub fn new(context: &str) -> Self {
Self {
context: context.to_string(),
is_poisoned: false,
}
}
pub fn poisoned(context: &str) -> Self {
Self {
context: context.to_string(),
is_poisoned: true,
}
}
pub fn get_context(&self) -> &str {
&self.context
}
pub fn is_poisoned(&self) -> bool {
self.is_poisoned
}
}
impl fmt::Display for RiLockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_poisoned {
write!(f, "Lock poisoned during acquisition: {}", self.context)
} else {
write!(f, "Lock acquisition failed: {}", self.context)
}
}
}
impl std::error::Error for RiLockError {}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiLockError {
#[new]
fn new_py(context: String, is_poisoned: bool) -> Self {
Self {
context,
is_poisoned,
}
}
#[staticmethod]
fn create_from_context(context: String) -> Self {
Self {
context,
is_poisoned: false,
}
}
#[staticmethod]
fn create_poisoned(context: String) -> Self {
Self {
context,
is_poisoned: true,
}
}
#[getter]
fn is_poisoned_py(&self) -> bool {
self.is_poisoned
}
#[getter]
fn context(&self) -> String {
self.context.clone()
}
fn __str__(&self) -> String {
self.to_string()
}
fn __repr__(&self) -> String {
format!("RiLockError {{ context: {:?}, is_poisoned: {} }}", self.context, self.is_poisoned)
}
}
pub type RiLockResult<T> = Result<T, RiLockError>;
pub trait RwLockExtensions<T: Send + Sync> {
fn read_safe(&self, context: &str) -> RiLockResult<RwLockReadGuard<'_, T>>;
fn write_safe(&self, context: &str) -> RiLockResult<RwLockWriteGuard<'_, T>>;
fn try_read_safe(&self, context: &str) -> RiLockResult<Option<RwLockReadGuard<'_, T>>>;
fn try_write_safe(&self, context: &str) -> RiLockResult<Option<RwLockWriteGuard<'_, T>>>;
}
impl<T: Send + Sync> RwLockExtensions<T> for RwLock<T> {
fn read_safe(&self, context: &str) -> RiLockResult<RwLockReadGuard<'_, T>> {
RwLock::read(self).map_err(|_| {
RiLockError::poisoned(context)
})
}
fn write_safe(&self, context: &str) -> RiLockResult<RwLockWriteGuard<'_, T>> {
RwLock::write(self).map_err(|_| {
RiLockError::poisoned(context)
})
}
fn try_read_safe(&self, context: &str) -> RiLockResult<Option<RwLockReadGuard<'_, T>>> {
match RwLock::try_read(self) {
Ok(guard) => Ok(Some(guard)),
Err(std::sync::TryLockError::Poisoned(_)) => {
Err(RiLockError::poisoned(context))
}
Err(std::sync::TryLockError::WouldBlock) => Ok(None),
}
}
fn try_write_safe(&self, context: &str) -> RiLockResult<Option<RwLockWriteGuard<'_, T>>> {
match RwLock::try_write(self) {
Ok(guard) => Ok(Some(guard)),
Err(std::sync::TryLockError::Poisoned(_)) => {
Err(RiLockError::poisoned(context))
}
Err(std::sync::TryLockError::WouldBlock) => Ok(None),
}
}
}
pub trait MutexExtensions<T: Send> {
fn lock_safe(&self, context: &str) -> RiLockResult<MutexGuard<'_, T>>;
fn try_lock_safe(&self, context: &str) -> RiLockResult<Option<MutexGuard<'_, T>>>;
}
impl<T: Send> MutexExtensions<T> for Mutex<T> {
fn lock_safe(&self, context: &str) -> RiLockResult<MutexGuard<'_, T>> {
Mutex::lock(self).map_err(|_| {
RiLockError::poisoned(context)
})
}
fn try_lock_safe(&self, context: &str) -> RiLockResult<Option<MutexGuard<'_, T>>> {
match Mutex::try_lock(self) {
Ok(guard) => Ok(Some(guard)),
Err(std::sync::TryLockError::Poisoned(_)) => {
Err(RiLockError::poisoned(context))
}
Err(std::sync::TryLockError::WouldBlock) => Ok(None),
}
}
}
pub fn from_poison_error<T>(_error: PoisonError<T>, context: &str) -> RiLockError {
RiLockError::poisoned(context)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
#[test]
fn test_read_safe_success() {
let lock = RwLock::new(42);
let result = lock.read_safe("test counter");
assert!(result.is_ok());
assert_eq!(*result.unwrap(), 42);
}
#[test]
fn test_write_safe_success() {
let lock = RwLock::new(42);
let result = lock.write_safe("test counter");
assert!(result.is_ok());
assert_eq!(*result.unwrap(), 42);
}
#[test]
fn test_try_read_safe_available() {
let lock = RwLock::new(42);
let result = lock.try_read_safe("test counter");
assert!(result.is_ok());
assert!(result.unwrap().is_some());
}
#[test]
fn test_try_write_safe_unavailable() {
let lock = RwLock::new(42);
let _write_guard = lock.write_safe("test counter").unwrap();
let result = lock.try_write_safe("test counter");
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_mutex_lock_safe() {
let mutex = Mutex::new(42);
let result = mutex.lock_safe("test mutex");
assert!(result.is_ok());
assert_eq!(*result.unwrap(), 42);
}
#[test]
fn test_lock_error_display() {
let error = RiLockError::new("test context");
assert_eq!(error.to_string(), "Lock acquisition failed: test context");
let poisoned = RiLockError::poisoned("poisoned lock");
assert_eq!(poisoned.to_string(), "Lock poisoned during acquisition: poisoned lock");
assert!(poisoned.is_poisoned());
}
#[test]
fn test_concurrent_reads() {
let lock = Arc::new(RwLock::new(0));
let num_threads = 10;
let iterations = 1000;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let lock = Arc::clone(&lock);
thread::spawn(move || {
for _ in 0..iterations {
let _guard = lock.read_safe("concurrent read").unwrap();
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(*lock.read_safe("final read").unwrap(), 0);
}
#[test]
fn test_concurrent_writes() {
let lock = Arc::new(RwLock::new(AtomicU64::new(0)));
let num_threads = 4;
let iterations = 100;
let handles: Vec<_> = (0..num_threads)
.map(|_i| {
let lock = Arc::clone(&lock);
thread::spawn(move || {
for _ in 0..iterations {
let guard = lock.write_safe("concurrent write").unwrap();
guard.fetch_add(1, Ordering::SeqCst);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let final_value = lock.read_safe("final value").unwrap().load(Ordering::SeqCst);
assert_eq!(final_value, (num_threads * iterations) as u64);
}
}