use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
const BASE_READ_TIMEOUT: Duration = Duration::from_secs(10);
const BASE_WRITE_TIMEOUT: Duration = Duration::from_secs(10);
const SPIN_INTERVAL: Duration = Duration::from_millis(20);
pub fn read<T: Send + Sync + 'static>(
lock: &Arc<RwLock<T>>,
context: &str,
) -> Option<OwnedRwLockReadGuard<T>> {
let timeout = crate::core::io_health::adaptive_timeout(BASE_READ_TIMEOUT);
let deadline = std::time::Instant::now() + timeout;
loop {
if let Ok(guard) = lock.clone().try_read_owned() {
return Some(guard);
}
if std::time::Instant::now() >= deadline {
crate::core::io_health::record_freeze();
tracing::warn!(
"bounded_lock: read timeout ({}ms) for {context}; degrading gracefully",
timeout.as_millis()
);
return None;
}
std::thread::sleep(SPIN_INTERVAL);
}
}
pub fn write<T: Send + Sync + 'static>(
lock: &Arc<RwLock<T>>,
context: &str,
) -> Option<OwnedRwLockWriteGuard<T>> {
let timeout = crate::core::io_health::adaptive_timeout(BASE_WRITE_TIMEOUT);
let deadline = std::time::Instant::now() + timeout;
loop {
if let Ok(guard) = lock.clone().try_write_owned() {
return Some(guard);
}
if std::time::Instant::now() >= deadline {
crate::core::io_health::record_freeze();
tracing::warn!(
"bounded_lock: write timeout ({}ms) for {context}; degrading gracefully",
timeout.as_millis()
);
return None;
}
std::thread::sleep(SPIN_INTERVAL);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_succeeds_on_uncontested_lock() {
let lock = Arc::new(RwLock::new(42u32));
let guard = read(&lock, "test").expect("uncontested read must succeed");
assert_eq!(*guard, 42);
}
#[test]
fn write_succeeds_on_uncontested_lock() {
let lock = Arc::new(RwLock::new(0u32));
let mut guard = write(&lock, "test").expect("uncontested write must succeed");
*guard = 7;
assert_eq!(*guard, 7);
}
#[test]
fn multiple_readers_concurrent() {
let lock = Arc::new(RwLock::new(99u32));
let g1 = read(&lock, "r1").expect("first reader");
let g2 = read(&lock, "r2").expect("second reader");
assert_eq!(*g1, 99);
assert_eq!(*g2, 99);
}
#[test]
fn write_excludes_readers() {
let lock = Arc::new(RwLock::new(0u32));
let _hold = lock.clone().try_write_owned().unwrap();
assert!(lock.clone().try_read_owned().is_err());
}
}