fast-cache 0.1.0

Embedded-first thread-per-core in-memory cache with optional Redis-compatible server
Documentation
use std::collections::HashMap;
use std::thread;

use super::*;
use crate::storage::{hash_key, stripe_index};

#[test]
fn shared_store_matches_hash_map_for_point_keys() {
    let store = SharedEmbeddedStore::<16>::new(SharedEmbeddedConfig::default());
    let mut oracle = HashMap::new();

    for index in 0..1_000usize {
        let key = SharedBytes::from(format!("key-{index}"));
        let value = SharedBytes::from(format!("value-{index}"));
        store.insert(key.clone(), value.clone());
        oracle.insert(key, value);
    }

    for (key, value) in oracle {
        let stored = store.get(key.as_ref()).expect("value exists");
        assert_eq!(stored.value(), value.as_ref());
    }
}

#[test]
fn route_key_uses_shift_based_striping() {
    let store = SharedEmbeddedStore::<16>::new(SharedEmbeddedConfig::default());
    let key = b"route-me";
    let route = store.route_key(key);
    assert_eq!(route.shard_id, stripe_index(hash_key(key), shift_for(16)));
}

#[test]
fn cloned_handles_work_from_multiple_threads() {
    let store = SharedEmbeddedStore::<16>::new(SharedEmbeddedConfig::default());
    let mut workers = Vec::new();

    for worker_id in 0..8usize {
        let store = store.clone();
        workers.push(thread::spawn(move || {
            for index in 0..1_250usize {
                let key = SharedBytes::from(format!("worker-{worker_id}-key-{index}"));
                let value = SharedBytes::from(format!("worker-{worker_id}-value-{index}"));
                store.insert(key.clone(), value.clone());
                assert_eq!(store.get(key.as_ref()).unwrap().value(), value.as_ref());
            }
        }));
    }

    for worker in workers {
        worker.join().expect("worker");
    }

    for worker_id in 0..8usize {
        for index in 0..1_250usize {
            let key = format!("worker-{worker_id}-key-{index}");
            let value = format!("worker-{worker_id}-value-{index}");
            assert_eq!(store.get(key.as_bytes()).unwrap().value(), value.as_bytes());
        }
    }
}

#[test]
fn entry_and_mutation_guards_update_values() {
    let store = SharedEmbeddedStore::<4>::new(SharedEmbeddedConfig::default());
    let key = SharedBytes::from_static(b"entry-key");
    let value = SharedBytes::from_static(b"first");
    store.entry(key.clone()).or_insert(value);
    assert_eq!(store.get(key.as_ref()).unwrap().value(), b"first");

    let mut value = store.get_mut(key.as_ref()).expect("entry exists");
    value.set(SharedBytes::from_static(b"second"));
    assert_eq!(value.value(), Some(b"second".as_slice()));
    drop(value);

    assert_eq!(
        store.remove(key.as_ref()).expect("removed").as_ref(),
        b"second"
    );
    assert!(!store.contains_key(key.as_ref()));
}

#[test]
fn fair_lock_policy_roundtrips() {
    let store = SharedEmbeddedStore::<4>::new(SharedEmbeddedConfig {
        lock_policy: SharedEmbeddedLockPolicy::Fair,
        ..SharedEmbeddedConfig::default()
    });
    store.insert_slice(b"fair-key", b"fair-value");
    assert_eq!(store.get(b"fair-key").unwrap().value(), b"fair-value");
    store.insert_slice(b"fair-key", b"updated");
    assert_eq!(store.get(b"fair-key").unwrap().value(), b"updated");
}

#[test]
fn ttl_values_are_filtered_from_shared_reads() {
    let store = SharedEmbeddedStore::<4>::new(SharedEmbeddedConfig::default());
    store.insert_slice_with_ttl(b"ttl-key", b"value", Some(2));
    assert_eq!(store.get(b"ttl-key").unwrap().value(), b"value");

    std::thread::sleep(std::time::Duration::from_millis(5));

    assert!(store.get(b"ttl-key").is_none());
    assert!(!store.contains_key(b"ttl-key"));
    assert!(store.get_mut(b"ttl-key").is_none());
}

#[test]
fn session_api_roundtrips() {
    let store = SharedEmbeddedStore::<8>::new(SharedEmbeddedConfig {
        route_mode: EmbeddedRouteMode::SessionPrefix,
        ..SharedEmbeddedConfig::default()
    });
    store.set_session(b"session-a", b"chunk-1", b"value-1");
    assert_eq!(
        store
            .get_session(b"session-a", b"chunk-1")
            .expect("session value")
            .value(),
        b"value-1"
    );
    assert!(store.get_session(b"session-b", b"chunk-1").is_none());
}

#[test]
fn per_stripe_memory_limit_evicts_independently() {
    let limit = 64usize;
    let store = SharedEmbeddedStore::<4>::new(SharedEmbeddedConfig {
        total_memory_bytes: Some(limit * 4),
        eviction_policy: EvictionPolicy::Lru,
        ..SharedEmbeddedConfig::default()
    });

    for shard_id in 0..4usize {
        for index in 0..64usize {
            let key = format!("stripe-{shard_id}-{index}");
            let route = store.route_key(key.as_bytes());
            if route.shard_id == shard_id {
                store.insert_slice(key.as_bytes(), b"0123456789abcdef");
            }
        }
    }

    for shard in &store.inner.shards {
        assert!(shard.read().stored_bytes() <= limit);
    }
}