From 8eab346790bb8ce14c84821d84e5541b37a14b06 Mon Sep 17 00:00:00 2001 From: SF-Zhou Date: Tue, 30 Dec 2025 14:19:43 +0800 Subject: [PATCH 1/3] use random hash state for shards --- src/shards_map.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shards_map.rs b/src/shards_map.rs index be4b674..97a78fd 100644 --- a/src/shards_map.rs +++ b/src/shards_map.rs @@ -1,4 +1,4 @@ -use foldhash::fast::{FixedState, RandomState}; +use foldhash::fast::RandomState; use std::borrow::Borrow; use std::collections::HashMap; use std::hash::{BuildHasher, Hash}; @@ -195,6 +195,7 @@ where pub struct ShardsMap { /// The vector of `ShardMap` instances. shards: Vec>, + hasher: RandomState, } impl ShardsMap @@ -217,6 +218,7 @@ where shards: (0..shard_amount) .map(|_| ShardMap::with_capacity(shard_capacity)) .collect::>(), + hasher: RandomState::default(), } } @@ -311,7 +313,7 @@ where K: Borrow, Q: Eq + Hash + ?Sized, { - let idx = FixedState::default().hash_one(key) as usize % self.shards.len(); + let idx = self.hasher.hash_one(key) as usize % self.shards.len(); &self.shards[idx] } } From 6dfe4ffd22e2dac0e23be9ad4e7eb6a41b221189 Mon Sep 17 00:00:00 2001 From: SF-Zhou Date: Tue, 30 Dec 2025 14:25:19 +0800 Subject: [PATCH 2/3] chore: add more comments --- Cargo.toml | 2 +- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 1 + src/lockmap.rs | 13 +++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b883379..1145741 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lockmap" -version = "0.1.15" +version = "0.1.16" edition = "2021" authors = ["SF-Zhou "] diff --git a/README.md b/README.md index 80144ab..cce7f00 100644 --- a/README.md +++ b/README.md @@ -6,37 +6,73 @@ [![Documentation](https://docs.rs/lockmap/badge.svg)](https://docs.rs/lockmap) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FSF-Zhou%2Flockmap.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FSF-Zhou%2Flockmap?ref=badge_shield) -A high-performance, thread-safe HashMap implementation for Rust that provides fine-grained locking at the key level. +**LockMap** is a high-performance, thread-safe HashMap implementation for Rust that provides **fine-grained locking at the key level**. + +Unlike standard concurrent maps that might lock the entire map or large buckets, `LockMap` allows you to hold an exclusive lock on a specific key (including non-existent ones) for complex atomic operations, minimizing contention across different keys. + +## Features + +* **Key-Level Locking**: Acquire exclusive locks for specific keys. Operations on different keys run in parallel. +* **Sharding Architecture**: Internal sharding reduces contention on the map structure itself during insertions and removals. +* **Deadlock Prevention**: Provides `batch_lock` to safely acquire locks on multiple keys simultaneously using a deterministic order. +* **Efficient Waiting**: Uses a hybrid spin-then-park Futex implementation for low-overhead locking. +* **Entry API**: Ergonomic RAII guards (`EntryByVal`, `EntryByRef`) for managing locks. + +## Important Caveats + +### 1. No Lock Poisoning + +Unlike `std::sync::Mutex`, **this library does not implement lock poisoning**. If a thread panics while holding an `Entry`, the lock is released immediately (via Drop) to avoid deadlocks, but the data is **not** marked as poisoned. +> **Warning**: Users must ensure exception safety. If a panic occurs during a partial update, the data associated with that key may be left in an inconsistent state for subsequent readers. + +### 2. `get()` Performance + +The `map.get(key)` method clones the value while holding an internal shard lock. +> **Note**: If your value type `V` is expensive to clone (e.g., deep copy of large structures), or if `clone()` acquires other locks, use `map.entry(key).get()` instead. This moves the clone operation outside the internal map lock, preventing blocking of other threads accessing the same shard. ## Usage ```rust use lockmap::LockMap; +use std::collections::BTreeSet; // Create a new lock map let map = LockMap::::new(); -// Set a value +// 1. Basic Insert map.insert_by_ref("key", "value".into()); -// Get a value +// 2. Get a value (Clones the value) assert_eq!(map.get("key"), Some("value".into())); -// Use entry API for exclusive access +// 3. Entry API: Exclusive access (Read/Write) +// This locks ONLY "key", other threads can access "other_key" concurrently. { let mut entry = map.entry_by_ref("key"); + + // Check value assert_eq!(entry.get().as_deref(), Some("value")); + + // Update value atomicity entry.insert("new value".to_string()); -} +} // Lock is automatically released here -// Remove a value +// 4. Remove a value assert_eq!(map.remove("key"), Some("new value".into())); -// Batch lock. -let mut keys = std::collections::BTreeSet::new(); +// 5. Batch Locking (Deadlock safe) +// Acquires locks for multiple keys in a deterministic order. +let mut keys = BTreeSet::new(); keys.insert("key1".to_string()); keys.insert("key2".to_string()); + +// `locked_entries` holds all the locks let mut locked_entries = map.batch_lock::>(keys); + +if let Some(mut entry) = locked_entries.get_mut("key1") { + entry.insert("updated_in_batch".into()); +} +// All locks released when `locked_entries` is dropped ``` ## License diff --git a/src/lib.rs b/src/lib.rs index fff7f94..853bfc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! - Entry API for exclusive access to values //! - Efficient concurrent operations through sharding //! - Safe atomic updates +//! - No poisoning, the lock is released normally on panic //! //! # Examples //! ``` diff --git a/src/lockmap.rs b/src/lockmap.rs index 8fa4654..12f38d6 100644 --- a/src/lockmap.rs +++ b/src/lockmap.rs @@ -61,6 +61,12 @@ impl State { StateFlags(self.flags.load(Ordering::Acquire)) } + /// Increments the reference count. + /// + /// # Note + /// + /// The reference count uses 31 bits, supporting up to 2^31 concurrent references. + /// Overflow is not checked; exceeding this limit causes undefined behavior. fn inc_ref(&self) -> StateFlags { StateFlags(self.flags.fetch_add(1, Ordering::AcqRel) + 1) } @@ -261,6 +267,13 @@ impl LockMap { /// * `Some(V)` if the key exists /// * `None` if the key doesn't exist /// + /// # Performance Note + /// + /// When no other thread holds an entry for this key, the `clone()` operation + /// is performed while holding the shard lock. If `V::clone()` is expensive, + /// consider using `entry()` or `entry_by_ref()` combined with `Entry::get()` + /// to avoid blocking other keys in the same shard. + /// /// **Locking behaviour:** Deadlock if called when holding the same entry. /// /// # Examples From 2529ffd7968ca93baa27ebd472906f19440ad840 Mon Sep 17 00:00:00 2001 From: SF-Zhou Date: Tue, 30 Dec 2025 14:29:23 +0800 Subject: [PATCH 3/3] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cce7f00..04b3442 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ assert_eq!(map.get("key"), Some("value".into())); // Check value assert_eq!(entry.get().as_deref(), Some("value")); - // Update value atomicity + // Update value atomically entry.insert("new value".to_string()); } // Lock is automatically released here