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())
}
}