use std::time::Duration;
use crate::DistkitError;
use crate::lock::tests::common::{make_options, make_options_with_rw_keys, raw_connection};
use crate::lock::{LockError, RwLock, LockGuardState};
#[tokio::test]
async fn concurrent_reads_share() {
let rw_a = RwLock::new(make_options("concurrent_reads_share").await);
let rw_b = RwLock::new(make_options("concurrent_reads_share").await);
let _read_a = rw_a.try_read().await.expect("first reader should acquire");
let _read_b = rw_b
.try_read()
.await
.expect("second reader should share the lock");
}
#[tokio::test]
async fn writer_excludes_readers() {
let writer = RwLock::new(make_options("writer_excludes_readers").await);
let reader = RwLock::new(make_options("writer_excludes_readers").await);
let _write_guard = writer.try_write().await.expect("writer should acquire");
match reader.try_read().await {
Err(DistkitError::LockError(LockError::AcquireFail)) => {}
other => panic!("expected AcquireFail while write held, got {other:?}"),
}
}
#[tokio::test]
async fn writer_waits_for_readers() {
let reader = RwLock::new(make_options("writer_waits_for_readers").await);
let writer = RwLock::new(make_options("writer_waits_for_readers").await);
let read_guard = reader.try_read().await.expect("reader should acquire");
let waiter = tokio::spawn(async move {
writer
.try_write_for(Duration::from_secs(2), Duration::from_millis(20))
.await
});
tokio::time::sleep(Duration::from_millis(200)).await;
read_guard.release().await.expect("read release should succeed");
waiter
.await
.expect("waiter task should not panic")
.expect("writer should acquire after readers release");
}
#[tokio::test]
async fn reader_waits_for_writer() {
let writer = RwLock::new(make_options("reader_waits_for_writer").await);
let reader = RwLock::new(make_options("reader_waits_for_writer").await);
let write_guard = writer.try_write().await.expect("writer should acquire");
let waiter = tokio::spawn(async move {
reader
.try_read_for(Duration::from_secs(2), Duration::from_millis(20))
.await
});
tokio::time::sleep(Duration::from_millis(200)).await;
write_guard
.release()
.await
.expect("write release should succeed");
waiter
.await
.expect("waiter task should not panic")
.expect("reader should acquire after writer releases");
}
#[tokio::test]
async fn waiting_writer_blocks_new_readers() {
let reader = RwLock::new(make_options("waiting_writer_blocks_new_readers").await);
let writer = RwLock::new(make_options("waiting_writer_blocks_new_readers").await);
let late_reader = RwLock::new(make_options("waiting_writer_blocks_new_readers").await);
let _read_guard = reader.try_read().await.expect("first reader should acquire");
let waiter = tokio::spawn(async move {
writer
.try_write_for(Duration::from_secs(2), Duration::from_millis(20))
.await
});
tokio::time::sleep(Duration::from_millis(150)).await;
match late_reader.try_read().await {
Err(DistkitError::LockError(LockError::AcquireFail)) => {}
other => panic!("expected AcquireFail — waiting writer should block new readers, got {other:?}"),
}
waiter.abort();
}
#[tokio::test]
async fn write_release_frees() {
let writer_a = RwLock::new(make_options("write_release_frees").await);
let writer_b = RwLock::new(make_options("write_release_frees").await);
let guard = writer_a.try_write().await.expect("writer should acquire");
guard.release().await.expect("write release should succeed");
writer_b
.try_write()
.await
.expect("write should be acquirable after release");
}
#[tokio::test]
async fn read_drop_frees() {
let reader = RwLock::new(make_options("read_drop_frees").await);
let writer = RwLock::new(make_options("read_drop_frees").await);
{
let _read_guard = reader.try_read().await.expect("reader should acquire");
}
tokio::time::sleep(Duration::from_millis(150)).await;
writer
.try_write()
.await
.expect("write should be acquirable after the reader drops");
}
#[tokio::test]
async fn auto_refresh_keeps_lease_alive() {
let mut options = make_options("auto_refresh_keeps_lease_alive").await;
options.ttl = Duration::from_millis(150);
let writer_a = RwLock::new(options);
let writer_b = RwLock::new(make_options("auto_refresh_keeps_lease_alive").await);
let _guard = writer_a.try_write().await.expect("writer should acquire");
tokio::time::sleep(Duration::from_millis(400)).await;
match writer_b.try_write().await {
Err(DistkitError::LockError(LockError::AcquireFail)) => {}
other => panic!("expected AcquireFail — refresh should keep lease alive, got {other:?}"),
}
}
#[tokio::test]
async fn get_state_reports_acquired() {
let reader = RwLock::new(make_options("get_state_reports_acquired").await);
let guard = reader.try_read().await.expect("reader should acquire");
assert_eq!(guard.get_state().await, LockGuardState::Acquired);
}
#[tokio::test]
async fn lost_lease_reports_lost() {
let (mut options, keys) = make_options_with_rw_keys("lost_lease_reports_lost").await;
options.ttl = Duration::from_millis(150);
let writer = RwLock::new(options);
let guard = writer.try_write().await.expect("writer should acquire");
let mut conn = raw_connection().await;
let _: () = redis::cmd("DEL")
.arg(&keys.writer)
.query_async(&mut conn)
.await
.expect("DEL should succeed");
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(
guard.get_state().await,
LockGuardState::Lost,
"lease should be marked lost after the writer key was deleted"
);
match guard.release().await {
Ok(LockGuardState::Lost) => {}
other => panic!("expected Ok(LockGuardState::Lost) after the lease was deleted, got {other:?}"),
}
}
#[tokio::test]
async fn try_write_does_not_block_readers() {
let reader = RwLock::new(make_options("try_write_does_not_block_readers").await);
let writer = RwLock::new(make_options("try_write_does_not_block_readers").await);
let late_reader = RwLock::new(make_options("try_write_does_not_block_readers").await);
let _read_guard = reader.try_read().await.expect("first reader should acquire");
match writer.try_write().await {
Err(DistkitError::LockError(LockError::AcquireFail)) => {}
other => panic!("expected AcquireFail for one-shot try_write, got {other:?}"),
}
late_reader
.try_read()
.await
.expect("reader should still acquire — one-shot try_write must not enqueue");
}