Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ConcurrentPulseMap

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

Construction

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
// 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 — 2.4–3.1x faster under contention.

Manual Resize

#![allow(unused)]
fn main() {
// 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)

#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
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())
}
}