pulse_map 0.6.4

A CPU cache-line hash table with zero-cost eviction. Every bucket fits in exactly one 64-byte cache line with embedded LFU+LRU eviction metadata.
Documentation
# ConcurrentPulseMap

Thread-safe map for production concurrent workloads. All methods take `&self` — no `Mutex` wrapping needed.

## Construction

```rust
use pulse_map::ConcurrentPulseMap;
use std::sync::Arc;

// Fixed-size
let map = Arc::new(ConcurrentPulseMap::<String, String>::new(1024));

// Auto-resize (doubles at 75% load)
let map = Arc::new(ConcurrentPulseMap::<String, u64>::with_auto_resize(256));
```

## Thread-Safe Operations

All methods take `&self` — safe to call from multiple threads simultaneously:

```rust
use std::thread;

let map = Arc::new(ConcurrentPulseMap::<u32, u32>::with_auto_resize(64));

// Spawn writers
let handles: Vec<_> = (0..8).map(|t| {
    let m = map.clone();
    thread::spawn(move || {
        for i in 0..10_000 {
            m.insert(t * 10_000 + i, i);
        }
    })
}).collect();

for h in handles { h.join().unwrap(); }

// Read from any thread — no lock needed
println!("Entries: {}", map.len());
```

## API

```rust
// Insert (thread-safe, no &mut needed)
map.insert("key".to_string(), "value".to_string());

// Insert with per-entry TTL (v0.6.1+)
map.insert_ttl("key".to_string(), "value".to_string(), 100u64);  // expires after 100 inserts
map.insert_ttl("key".to_string(), "value".to_string(), u64::MAX);  // never expires

// Get (updates eviction priority atomically)
let val: Option<String> = map.get(&"key".to_string());

// Peek (no priority update — pure read)
let val: Option<String> = map.peek(&"key".to_string());

// Remove
let existed: bool = map.remove(&"key".to_string());

// Contains
let exists: bool = map.contains_key(&"key".to_string());
```

> **Tip:** For 3+ threads, use [`ShardedPulseMap`]./api-sharded.md — 2.4–3.1x faster under contention.

## Manual Resize

```rust
// Force resize to 2048 buckets (8192 capacity)
map.resize(2048);
```

> ⚠️ **Resize is stop-the-world** — acquires exclusive write lock, blocking all operations until rehashing completes. This is brief (~1ms for 10K entries) but causes a latency spike.

## Stats (Lock-Free)

```rust
map.len()             // AtomicUsize — no lock
map.capacity()        // Acquires read lock (cheap)
map.load_factor()     // Derived from len/capacity
map.eviction_count()  // AtomicUsize — no lock
map.num_buckets()     // Acquires read lock
```

## Locking Model

```
Read operations (get, peek, contains, stats):
  └── RwLock::read() + AtomicU64 MetaWord read (deferred LRU/LFU via AccessBuffer)

Write operations (insert, remove):
  └── RwLock::read() + per-bucket spinlock

Resize:
  └── RwLock::write() (exclusive — blocks everything)
```

**Key insight:** Normal reads and writes only acquire a **read lock** on the RwLock, so they run concurrently. The per-bucket spinlock serializes access to the same bucket only. Reads use an `AtomicU64` MetaWord and `AccessBuffer` for deferred LRU/LFU updates, completely avoiding locks on read paths.

## Production Pattern

```rust
use pulse_map::ConcurrentPulseMap;
use std::sync::Arc;

// Shared application cache
struct AppState {
    cache: ConcurrentPulseMap<String, String>,
}

impl AppState {
    fn new() -> Self {
        Self {
            cache: ConcurrentPulseMap::with_auto_resize(4096),
        }
    }
}

// Use from any handler — no mutex needed
fn handle_request(state: &AppState, key: &str) -> Option<String> {
    state.cache.get(&key.to_string())
}
```