#![cfg(feature = "cache-redis")]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use rs_zero::{
cache_redis::{RedisBreakerConfig, RedisCacheConfig},
lock::{DistributedLock, LockError, RedisDistributedLock, RedisLockConfig},
};
#[tokio::test]
#[ignore = "requires RS_ZERO_TEST_REDIS_URL pointing to a running Redis instance"]
async fn redis_lock_acquire_contention_release_and_ttl_expiry() {
let url = std::env::var("RS_ZERO_TEST_REDIS_URL")
.unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
let lock = RedisDistributedLock::new(RedisLockConfig {
redis: RedisCacheConfig {
url,
..RedisCacheConfig::default()
},
namespace: format!("rs-zero-lock-{}", unique_suffix()),
breaker: RedisBreakerConfig::go_zero_defaults(),
..RedisLockConfig::default()
})
.expect("lock backend");
lock.health_check().await.expect("redis health");
let key = "order:{external-lock}";
let first = lock
.try_acquire(key, Duration::from_millis(250))
.await
.expect("first acquire");
assert!(
lock.acquire(key, Duration::from_millis(250))
.await
.expect("second acquire returns busy")
.is_none()
);
let stale_guard = first.clone();
lock.release(&first).await.expect("release owner");
assert!(
matches!(
lock.release(&stale_guard)
.await
.expect_err("second release mismatches"),
LockError::OwnerMismatch
),
"second release must not delete another owner's lock"
);
let expiring = lock
.try_acquire(key, Duration::from_millis(100))
.await
.expect("expiring acquire");
tokio::time::sleep(Duration::from_millis(180)).await;
let reacquired = lock
.try_acquire(key, Duration::from_millis(250))
.await
.expect("reacquire after ttl");
assert!(matches!(
lock.release(&expiring)
.await
.expect_err("expired owner mismatch"),
LockError::OwnerMismatch
));
lock.release(&reacquired).await.expect("release reacquired");
}
fn unique_suffix() -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("{nanos}-{}", std::process::id())
}