rs-zero 0.2.8

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
#![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())
}