### Test module setup with MockResource and FRUITS Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Defines a mock resource type implementing HasId and a static list of fruit names used across tests. Provides a helper function to create a vector of mock resources. ```rust #[cfg(test)] mod tests { use std::sync::LazyLock; use super::*; #[derive(Debug, Clone)] struct MockResource { pub name: String, } impl HasId for MockResource { fn id(&self) -> &str { &self.name } } static FRUITS: LazyLock> = LazyLock::new(|| { vec![ "apple".to_string(), "kumquat".to_string(), "litchi".to_string(), "papaya".to_string(), "pear".to_string(), "mangosteen".to_string(), "orange".to_string(), ] }); fn pick_some_fruit() -> Vec { let mut result = Vec::new(); for name in FRUITS.iter() { result.push(MockResource { name: name.clone() }); } result } } ``` -------------------------------- ### Test rendezvous_hash_basic_functionality Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Verifies that adding resources, locating a key, and consistency of location for the same key work. Also checks that different keys map to multiple resources. ```rust #[test] fn rendezvous_hash_basic_functionality() { let mut ring = RendezvousRing::new(); let resources = pick_some_fruit(); // Add resources for resource in &resources { ring.add(Box::new(resource.clone())); } // Test locate let key = "test-key"; let located = ring.locate(key); assert!(located.is_some()); let first_location = located.expect("should have location").id(); // Test consistency - same key should always map to same resource for _ in 0..10 { let located = ring.locate(key).expect("should have location"); assert_eq!(located.id(), first_location); } // Test different keys map to different resources (mostly) let mut locations = std::collections::HashSet::new(); for i in 0..100 { let key = format!("key-{}", i); if let Some(resource) = ring.locate(&key) { locations.insert(resource.id().to_string()); } } // Should use multiple resources assert!(locations.len() > 1); } ``` -------------------------------- ### RendezvousRing::resources_with_weights Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Lists all resources and their weights in the ring. ```APIDOC ## RendezvousRing::resources_with_weights ### Description Lists all resources and their weights in the ring. ### Method resources_with_weights ### Parameters None ### Returns - **Vec<(&dyn HasId, f64)>** - A vector of resource references and their weights. ``` -------------------------------- ### RendezvousRing::new Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Creates a new rendezvous hash ring with the default hash algorithm. ```APIDOC ## RendezvousRing::new ### Description Creates a new rendezvous hash ring with the default hash algorithm. ### Method new ### Parameters None ### Returns - **Self** - A new RendezvousRing instance. ``` -------------------------------- ### RendezvousRing get_weight and resources_with_weights methods Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Defines methods to retrieve the weight of a specific resource by its ID and to list all resources with their associated weights. Used for querying the ring's state. ```rust pub fn get_weight(&self, resource: &dyn HasId) -> Option { let id = resource.id(); self.resources .iter() .find(|(res, _)| res.id() == id) .map(|(_, weight)| *weight) } /// List all resources and their weights pub fn resources_with_weights(&self) -> Vec<(&dyn HasId, f64)> { self.resources .iter() .map(|(res, weight)| (res.as_ref(), *weight)) .collect() } } ``` -------------------------------- ### ConsistentRing API Source: https://docs.rs/lightcycle/latest/lightcycle/struct.ConsistentRing.html Methods for creating and managing a consistent hash ring, including adding, removing, and locating resources. ```APIDOC ## ConsistentRing::new ### Description Creates a new consistent hash ring with default settings. ### Method new ### Signature ```rust pub fn new() -> Self ``` ### Parameters None ### Returns - **Self** - A new ConsistentRing instance. ``` ```APIDOC ## ConsistentRing::new_with_replica_count ### Description Creates a new consistent hash ring with a specified number of replicas per resource. ### Method new_with_replica_count ### Signature ```rust pub fn new_with_replica_count(replicas: usize) -> Self ``` ### Parameters - **replicas** (usize) - Required - The number of replicas to use for each resource. ``` ```APIDOC ## ConsistentRing::new_with_hasher ### Description Creates a new consistent hash ring with a custom hasher. ### Method new_with_hasher ### Signature ```rust pub fn new_with_hasher(hasher: H) -> Self ``` ### Parameters - **hasher** (H) - Required - The hasher to use for hashing keys. ``` ```APIDOC ## ConsistentRing::new_with_hasher_and_replicas ### Description Creates a new consistent hash ring with a custom hasher and a specified number of replicas. ### Method new_with_hasher_and_replicas ### Signature ```rust pub fn new_with_hasher_and_replicas(hasher: H, replicas: usize) -> Self ``` ### Parameters - **hasher** (H) - Required - The hasher to use for hashing keys. - **replicas** (usize) - Required - The number of replicas to use for each resource. ``` ```APIDOC ## ConsistentRing::remove ### Description Removes a resource from the hash ring by its ID string. ### Method remove ### Signature ```rust pub fn remove(&mut self, id: &str) -> Result, String> ``` ### Parameters - **id** (&str) - Required - The ID string of the resource to remove. ### Returns - **Result, String>** - The removed resource on success, or an error message on failure. ``` ```APIDOC ## ConsistentRing::hasher_name ### Description Returns the name of the hasher used by the ring. ### Method hasher_name ### Signature ```rust pub fn hasher_name(&self) -> &'static str ``` ### Parameters None ### Returns - **&'static str** - The name of the hasher. ``` ```APIDOC ## ConsistentRing::hash_key ### Description Computes the raw hash value for a given key. Intended for testing purposes. ### Method hash_key ### Signature ```rust pub fn hash_key(&self, key: &str) -> u64 ``` ### Parameters - **key** (&str) - Required - The key to hash. ### Returns - **u64** - The raw hash value. ``` ```APIDOC ## HashRing::add ### Description Adds a new resource to the hash ring, storing replica keys distributed around the ring. ### Method add ### Signature ```rust fn add(&mut self, resource: Self::Resource) ``` ### Parameters - **resource** (Self::Resource) - Required - The resource to add. ``` ```APIDOC ## HashRing::remove ### Description Removes a resource from the hash ring. ### Method remove ### Signature ```rust fn remove(&mut self, resource: &Self::Resource) ``` ### Parameters - **resource** (&Self::Resource) - Required - The resource to remove. ``` ```APIDOC ## HashRing::locate ### Description Given an identifier, looks up the matching resource to use. The identifier is not a resource ID but something that needs to be placed on one of the managed resources. ### Method locate ### Signature ```rust fn locate(&self, id: &str) -> Option<&Self::Resource> ``` ### Parameters - **id** (&str) - Required - The identifier to locate. ### Returns - **Option<&Self::Resource>** - The matching resource, if found. ``` ```APIDOC ## HashRing::resource_count ### Description Returns the number of resources in the hash ring. ### Method resource_count ### Signature ```rust fn resource_count(&self) -> usize ``` ### Parameters None ### Returns - **usize** - The number of resources. ``` ```APIDOC ## HashRing::len ### Description Returns the total number of entries in the ring. ### Method len ### Signature ```rust fn len(&self) -> usize ``` ### Parameters None ### Returns - **usize** - The total number of entries. ``` ```APIDOC ## HashRing::is_empty ### Description Checks if the hash ring is empty. ### Method is_empty ### Signature ```rust fn is_empty(&self) -> bool ``` ### Parameters None ### Returns - **bool** - True if the ring is empty, false otherwise. ``` ```APIDOC ## HashRing::add_weighted ### Description Adds a new resource with a specific weight. For algorithms that don't support weighting, the weight is ignored and this behaves like `add()`. ### Method add_weighted ### Signature ```rust fn add_weighted(&mut self, resource: Self::Resource, _weight: f64) ``` ### Parameters - **resource** (Self::Resource) - Required - The resource to add. - **_weight** (f64) - Required - The weight to assign to the resource. ``` ```APIDOC ## HashRing::update_weight ### Description Updates the weight of an existing resource. Returns an error if the resource is not found or if the implementation doesn't support weight updates. ### Method update_weight ### Signature ```rust fn update_weight(&mut self, _resource: &Self::Resource, _weight: f64) -> Result<(), EndOfLine> ``` ### Parameters - **_resource** (&Self::Resource) - Required - The resource whose weight to update. - **_weight** (f64) - Required - The new weight. ### Returns - **Result<(), EndOfLine>** - Ok on success, or an error. ``` -------------------------------- ### Fruit Resource Generator Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html A static collection and helper function to generate mock resources for testing. ```rust static FRUITS: LazyLock> = LazyLock::new(|| { vec![ "apple".to_string(), "kumquat".to_string(), "litchi".to_string(), "papaya".to_string(), "pear".to_string(), "mangosteen".to_string(), "orange".to_string(), ] }); fn pick_some_fruit() -> Vec { let mut result = Vec::new(); for name in FRUITS.iter() { result.push(MockResource { name: name.clone() }); } result } ``` -------------------------------- ### RendezvousRing::new_with_hasher Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Creates a new rendezvous hash ring with a specific hash algorithm. ```APIDOC ## RendezvousRing::new_with_hasher ### Description Creates a new rendezvous hash ring with a specific hash algorithm. ### Method new_with_hasher ### Parameters - **hasher** (H) - Required - The hash algorithm to use. ### Returns - **Self** - A new RendezvousRing instance. ``` -------------------------------- ### Test rendezvous_hash_adding_same_resource_replaces Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Verifies that adding a resource with the same ID replaces the existing one rather than adding a duplicate. ```rust #[test] fn rendezvous_hash_adding_same_resource_replaces() { let mut ring = RendezvousRing::new(); let resource1 = MockResource { name: "duplicate".to_string(), }; let resource2 = MockResource { name: "duplicate".to_string(), }; ring.add(Box::new(resource1)); assert_eq!(ring.resource_count(), 1); // Adding same ID should replace, not add } ``` -------------------------------- ### Unit tests for hashing Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html Contains tests for Blake3Hasher consistency, different inputs, and the default hasher behavior. ```rust #[cfg(test)] mod tests { #[allow(unused_imports)] use super::*; #[test] #[cfg(feature = "hash-blake3")] fn blake3_consistency() { let hasher = Blake3Hasher::new(); let data = b"test data"; let hash1 = hasher.hash(data); let hash2 = hasher.hash(data); assert_eq!(hash1, hash2, "Hash should be consistent"); assert_ne!(hash1, 0, "Hash should not be zero"); } #[test] #[cfg(feature = "hash-blake3")] fn blake3_different_inputs() { let hasher = Blake3Hasher::new(); let hash1 = hasher.hash(b"test1"); let hash2 = hasher.hash(b"test2"); assert_ne!(hash1, hash2, "Different inputs should produce different hashes"); } #[test] #[cfg(any(feature = "hash-blake3", feature = "hash-xxhash"))] fn default_hasher() { let hasher = DefaultHasher::new(); let data = b"consistent hashing test"; let hash = hasher.hash(data); assert_ne!(hash, 0, "Default hasher should produce non-zero hash"); println!("Using hasher: {}", hasher.name()); } } ``` -------------------------------- ### RendezvousRing implementation with weighted rendezvous hashing Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Complete implementation of the RendezvousRing struct and its HashRing trait methods, including weighted rendezvous hashing with power scaling (hash^(1/weight)). The code defines the default hasher, add/remove/locate operations, and weighted resource management. It also provides constructors and utility methods for diagnostics and testing. ```rust //! The rendezvous hash data structure. //! //! The rendezvous hashing algorithm (also known as Highest Random Weight) /// assigns each key to the node with the highest `hash(key + node_id) * weight` value. /// This distributes the keys fairly by default while allowing you to bias the distribution /// toward weighted nodes if you want. You might, for example, have a node that has more memory capacity /// or a larger disk, and want to store more items there. use crate::hashes::{ConsistentHasher, DefaultHasher}; use crate::{EndOfLine, HasId, HashRing}; /// A rendezvous hash ring implementation with support for weighted nodes. #[derive(Debug)] pub struct RendezvousRing where H: ConsistentHasher, { /// The resources and their weights: (resource, weight) resources: Vec<(Box, f64)>, /// The hash function to use for rendezvous hashing algorithm: H, } impl Default for RendezvousRing where H: ConsistentHasher, { fn default() -> Self { Self { resources: Vec::new(), algorithm: H::new(), } } } impl HashRing for RendezvousRing where H: ConsistentHasher, { type Resource = Box; fn add(&mut self, item: Self::Resource) { // Default weight of 1.0 for unweighted adds self.add_weighted(item, 1.0); } fn remove(&mut self, resource: &Self::Resource) { let id = resource.id(); self.resources.retain(|(res, _)| res.id() != id); } fn locate(&self, id: &str) -> Option<&Self::Resource> { if self.resources.is_empty() { return None; } let mut best_node: Option<&Box> = None; let mut best_score = f64::NEG_INFINITY; for (resource, weight) in &self.resources { // Rendezvous algorithm: hash(key + node_id) * weight let combined_key = format!("{}{}", id, resource.id()); let hash_value = self.algorithm.hash(combined_key.as_bytes()); // Normalize hash to [0,1] then apply weight using power scaling // This uses the weighted rendezvous hashing approach: hash^(1/weight) let normalized_hash = hash_value as f64 / u64::MAX as f64; let score = if *weight > 0.0 { normalized_hash.powf(1.0 / weight) } else { f64::NEG_INFINITY // Invalid weight }; if score > best_score { best_score = score; best_node = Some(resource); } } best_node } fn resource_count(&self) -> usize { self.resources.len() } fn len(&self) -> usize { // For rendezvous hashing, length equals resource count (no replicas). // This is the simplification over the consistent hash. self.resources.len() } fn is_empty(&self) -> bool { self.resources.is_empty() } fn add_weighted(&mut self, resource: Self::Resource, weight: f64) { let id = resource.id().to_string(); // Remove any existing resource with the same ID first self.resources.retain(|(res, _)| res.id() != id); // Add the new resource with its weight self.resources.push((resource, weight)); } fn update_weight(&mut self, resource: &Self::Resource, weight: f64) -> Result<(), EndOfLine> { let id = resource.id(); for (res, current_weight) in &mut self.resources { if res.id() == id { *current_weight = weight; return Ok(()); } } Err(EndOfLine::NotFound { id: id.to_string() }) } } impl RendezvousRing { /// Create a new rendezvous hash ring with the default hash algorithm pub fn new() -> Self { Self::default() } } impl RendezvousRing where H: ConsistentHasher, { /// Create a new rendezvous hash ring with a specific algorithm pub fn new_with_hasher(hasher: H) -> Self { Self { resources: Vec::new(), algorithm: hasher, } } /// Get the hash algorithm name for diagnostics pub fn hash_algorithm_name(&self) -> &'static str { self.algorithm.name() } /// Get a raw hash value for testing purposes pub fn hash_key(&self, key: &str) -> u64 { self.algorithm.hash(key.as_bytes()) } /// Get the weight of a resource, if it exists ``` -------------------------------- ### Test Rendezvous Hashing with Weighted Resources Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Verifies that resources with higher weights receive a proportionally larger share of keys in the RendezvousRing. ```rust #[test] fn rendezvous_hash_weighted_resources() { let mut ring = RendezvousRing::new(); let light = MockResource { name: "light".to_string(), }; let heavy = MockResource { name: "heavy".to_string(), }; // Add with different weights ring.add_weighted(Box::new(light.clone()), 1.0); ring.add_weighted(Box::new(heavy.clone()), 10.0); // Count distribution let mut light_count = 0; let mut heavy_count = 0; for i in 0..1000 { let key = format!("test-key-{}", i); if let Some(located) = ring.locate(&key) { if located.id() == "light" { light_count += 1; } else { heavy_count += 1; } } } // Heavy should get significantly more keys assert!(heavy_count > light_count * 5); } ``` -------------------------------- ### RapidHashQualityHasher implementation using SeedableState::fixed Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html RapidHash quality hasher implementation, gated behind the hash-rapidhash feature. Uses rapidhash::quality::SeedableState::fixed() and hash_one to produce a u64 hash. ```rust /// RapidHash quality hasher implementation (high-quality distribution) #[cfg(feature = "hash-rapidhash")] #[derive(Clone)] pub struct RapidHashQualityHasher; #[cfg(feature = "hash-rapidhash")] impl ConsistentHasher for RapidHashQualityHasher { fn new() -> Self { RapidHashQualityHasher } fn hash(&self, data: &[u8]) -> u64 { use std::hash::BuildHasher; use rapidhash::quality::SeedableState; let hasher = SeedableState::fixed(); hasher.hash_one(data) } fn name(&self) -> &'static str { "rapidhash-quality" } } ``` -------------------------------- ### ConsistentRing Replica Movement Test Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html Tests how adding replicas affects resource distribution and location consistency. ```rust #[test] fn adding_new_replicas_moves_locations() { let fruits = pick_some_fruit(); let mut fruit_iter = fruits.into_iter(); let mut ring = ConsistentRing::new_with_replica_count(2); let f = fruit_iter.next().expect("there should be a first fruitd"); ring.add(Box::new(f)); assert_eq!(ring.len(), 2); assert_eq!(ring.resource_count(), 1); let location = ring .locate("nom nom nom") .expect("everything should have a home of some kind"); assert_eq!(location.id(), "apple"); for f in fruit_iter { ring.add(Box::new(f)); } assert_eq!(ring.len(), FRUITS.len() * 2); assert_eq!(ring.resource_count(), FRUITS.len()); let location = ring .locate("nom nom nom") .expect("everything should have a home of some kind"); // The exact fruit depends on hash algorithm and formatting // Just verify we get a consistent result assert!( location.id() == "litchi" || location.id() == "pear", "Got unexpected fruit: {}", location.id() ); // These tests just verify consistent hashing works, not specific values let location2 = ring .locate("asdfasdfasdfsafasdf") .expect("everything should have a home of some kind"); assert!(FRUITS.iter().any(|f| f == location2.id())); let location3 = ring.locate("1").expect("everything should have a home of some kind"); assert!(FRUITS.iter().any(|f| f == location3.id())); } ``` -------------------------------- ### MockResource Implementation Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html A helper struct used for testing that implements the HasId trait. ```rust use super::*; #[derive(Debug, Clone)] struct MockResource { pub name: String, } impl HasId for MockResource { fn id(&self) -> &str { &self.name } } ``` -------------------------------- ### Test Rendezvous Hashing Weight Updates Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Demonstrates updating the weight of an existing resource and handling errors when attempting to update a non-existent resource. ```rust #[test] fn rendezvous_hash_weight_updates() { let mut ring = RendezvousRing::new(); let resource = MockResource { name: "weighted".to_string(), }; let boxed_resource: Box = Box::new(resource.clone()); // Add with initial weight ring.add_weighted(Box::new(resource.clone()), 5.0); assert_eq!(ring.get_weight(boxed_resource.as_ref()), Some(5.0)); // Update weight assert!(ring.update_weight(&boxed_resource, 10.0).is_ok()); assert_eq!(ring.get_weight(boxed_resource.as_ref()), Some(10.0)); // Try to update non-existent resource let nonexistent = MockResource { name: "nonexistent".to_string(), }; let boxed_nonexistent: Box = Box::new(nonexistent); assert!(ring.update_weight(&boxed_nonexistent, 5.0).is_err()); } ``` -------------------------------- ### ConsistentRing Location Test Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html Verifies that resources are correctly located within a ring with a single replica count. ```rust #[test] fn locations_behave_as_expected() { // This test knows about how we generate id hashes. // First, make a zero-replicas ring. let mut ring = ConsistentRing::new_with_replica_count(1); ring.add(Box::new(MockResource { name: "pecan".to_string(), })); ring.add(Box::new(MockResource { name: "walnut".to_string(), })); let location = ring.locate("pecan0").expect("pecan0 should be there"); assert_eq!(location.id(), "pecan"); let location = ring.locate("walnut0").expect("walnut0 should be there"); assert_eq!(location.id(), "walnut"); } ``` -------------------------------- ### RapidHashFastHasher implementation using SeedableState::fixed Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html RapidHash fast hasher implementation, gated behind the hash-rapidhash feature. Uses rapidhash::fast::SeedableState::fixed() and hash_one to produce a u64 hash, optimized for speed. ```rust /// RapidHash fast hasher implementation (optimized for speed) #[cfg(feature = "hash-rapidhash")] #[derive(Clone)] pub struct RapidHashFastHasher; #[cfg(feature = "hash-rapidhash")] impl ConsistentHasher for RapidHashFastHasher { fn new() -> Self { RapidHashFastHasher } fn hash(&self, data: &[u8]) -> u64 { use std::hash::BuildHasher; use rapidhash::fast::SeedableState; let hasher = SeedableState::fixed(); hasher.hash_one(data) } fn name(&self) -> &'static str { "rapidhash-fast" } } ``` -------------------------------- ### Test rendezvous_hash_single_node Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Verifies that with a single resource, all keys map to that resource and the ring is not empty. ```rust #[test] fn rendezvous_hash_single_node() { let mut ring = RendezvousRing::new(); let resource = MockResource { name: "single".to_string(), }; ring.add(Box::new(resource)); assert_eq!(ring.resource_count(), 1); assert!(!ring.is_empty()); // Everything should map to the single node for i in 0..10 { let key = format!("key-{}", i); let located = ring.locate(&key).expect("should find single node"); assert_eq!(located.id(), "single"); } } ``` -------------------------------- ### ConsistentRing struct and implementation Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html The core structure for consistent hashing, including the default implementation and the HashRing trait implementation for resource management. ```rust use std::collections::{BTreeMap, HashMap}; use crate::hashes::{ConsistentHasher, DefaultHasher}; use crate::{HasId, HashRing}; /// A consistent hash ring implementation. #[derive(Debug)] pub struct ConsistentRing where H: ConsistentHasher, { /// The number of replicas of each resource to insert into the ring. Ring size = replicas * entries. replicas: usize, /// The resources we're tracking. resources: HashMap>, /// The consistent hash ring itself: each entry points to a key in the resource map. hashring: BTreeMap, /// The hash function to use hasher: H, } impl Default for ConsistentRing { fn default() -> Self { let replicas = 4; // defaulting to pretty small let resources = HashMap::new(); let hashring = BTreeMap::new(); let hasher = DefaultHasher::new(); Self { replicas, resources, hashring, hasher, } } } impl HashRing for ConsistentRing where H: ConsistentHasher, { type Resource = Box; fn add(&mut self, resource: Self::Resource) { let id = resource.id(); for i in 0..self.replicas { let hashitem = format!("{}{}", id, i); let replica_hash = self.hasher.hash(hashitem.as_bytes()); self.hashring.insert(replica_hash, id.to_owned()); } self.resources.insert(id.to_owned(), resource); } fn remove(&mut self, resource: &Self::Resource) { let id = resource.id(); for i in 0..self.replicas { let hashitem = format!("{}{}", id, i); let replica_hash = self.hasher.hash(hashitem.as_bytes()); self.hashring.remove(&replica_hash); } self.resources.remove(id); } fn locate(&self, id: &str) -> Option<&Self::Resource> { let hashed_id = self.hasher.hash(id.as_bytes()); // This search is the heart of the consistent hash ring concept. // BTreeMap::range gives us an O(log n) seek to the first key >= hashed_id. if let Some((_hash, resource_id)) = self.hashring.range(hashed_id..).next() { self.resources.get(resource_id) } else if let Some((_hash, resource_id)) = self.hashring.last_key_value() { // We're past the end, so we take the last node. self.resources.get(resource_id) } else { // This case happens if the ring is empty. People who do that get what they deserve. None } } fn resource_count(&self) -> usize { self.resources.len() } fn len(&self) -> usize { self.hashring.len() } fn is_empty(&self) -> bool { self.hashring.is_empty() } } impl ConsistentRing { pub fn new() -> Self { Self::default() } pub fn new_with_replica_count(replicas: usize) -> Self { Self { replicas, resources: HashMap::new(), hashring: BTreeMap::new(), hasher: DefaultHasher::new(), } } } impl ConsistentRing where H: ConsistentHasher, { pub fn new_with_hasher(hasher: H) -> Self { Self { replicas: 4, resources: HashMap::new(), hashring: BTreeMap::new(), hasher, } } pub fn new_with_hasher_and_replicas(hasher: H, replicas: usize) -> Self { Self { replicas, resources: HashMap::new(), hashring: BTreeMap::new(), hasher, } } /// Remove a resource by its ID string pub fn remove(&mut self, id: &str) -> Result, String> { // First remove from hashring for i in 0..self.replicas { let hashitem = format!("{}{}", id, i); let replica_hash = self.hasher.hash(hashitem.as_bytes()); self.hashring.remove(&replica_hash); } // Then remove from resources and return it self.resources .remove(id) .ok_or_else(|| format!("Resource '{}' not found", id)) } pub fn hasher_name(&self) -> &'static str { self.hasher.name() } /// Get a raw hash value for testing purposes pub fn hash_key(&self, key: &str) -> u64 { self.hasher.hash(key.as_bytes()) } } #[cfg(test)] mod tests { use std::sync::LazyLock; ``` -------------------------------- ### HashRing::add Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Adds a new resource to the hash ring. Stores replica keys distributed around the ring. ```APIDOC ## HashRing::add ### Description Adds a new resource to the hash ring. Stores replica keys distributed around the ring. ### Method add ### Parameters - **item** (Self::Resource) - Required - The resource to add. ### Returns None ``` -------------------------------- ### Test rendezvous_hash_empty_ring Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Checks that an empty ring has zero resources, is empty, and locate returns None for any key. ```rust #[test] fn rendezvous_hash_empty_ring() { let ring = RendezvousRing::new(); assert_eq!(ring.resource_count(), 0); assert!(ring.is_empty()); assert!(ring.locate("any-key").is_none()); } ``` -------------------------------- ### XXHasher implementation using xxh3_64 Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html XXHash hasher implementation, gated behind the hash-xxhash feature. Uses xxhash_rust::xxh3::xxh3_64 to compute a 64-bit hash directly from the input data. ```rust /// XXHash hasher implementation (fastest, non-cryptographic) #[cfg(feature = "hash-xxhash")] #[derive(Clone)] pub struct XXHasher; #[cfg(feature = "hash-xxhash")] impl ConsistentHasher for XXHasher { fn new() -> Self { XXHasher } fn hash(&self, data: &[u8]) -> u64 { use xxhash_rust::xxh3::xxh3_64; xxh3_64(data) } fn name(&self) -> &'static str { "xxhash" } } ``` -------------------------------- ### HashRing::locate Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Given an identifier, looks up the matching resource to use. The id is not a resource id, but something that needs to be placed on one of the managed resources. ```APIDOC ## HashRing::locate ### Description Given an identifier, looks up the matching resource to use. The id is not a resource id, but something that needs to be placed on one of the managed resources. ### Method locate ### Parameters - **id** (&str) - Required - The identifier to locate. ### Returns - **Option<&Self::Resource>** - The resource to use, or None if not found. ``` -------------------------------- ### HashRing::len Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Returns the total number of entries in the ring. ```APIDOC ## HashRing::len ### Description Returns the total number of entries in the ring. ### Method len ### Parameters None ### Returns - **usize** - The total number of entries. ``` -------------------------------- ### MetroHasher implementation using MetroHash64 Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html MetroHash hasher implementation, gated behind the hash-metrohash feature. Uses std::hash::Hasher and metrohash::MetroHash64 to write data and finish to a u64. ```rust /// MetroHash hasher implementation (fast, high-quality) #[cfg(feature = "hash-metrohash")] #[derive(Clone)] pub struct MetroHasher; #[cfg(feature = "hash-metrohash")] impl ConsistentHasher for MetroHasher { fn new() -> Self { MetroHasher } fn hash(&self, data: &[u8]) -> u64 { use std::hash::Hasher; use metrohash::MetroHash64; let mut hasher = MetroHash64::new(); hasher.write(data); hasher.finish() } fn name(&self) -> &'static str { "metrohash" } } ``` -------------------------------- ### Murmur3Hasher implementation using murmur3_x64_128 Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html Murmur3 hasher implementation, gated behind the hash-murmur3 feature. Uses murmurs::murmur3_x64_128 with seed 0 and takes the first u64 of the 128-bit result. ```rust /// Murmur3 hasher implementation (popular, good distribution) #[cfg(feature = "hash-murmur3")] #[derive(Clone)] pub struct Murmur3Hasher; #[cfg(feature = "hash-murmur3")] impl ConsistentHasher for Murmur3Hasher { fn new() -> Self { Murmur3Hasher } fn hash(&self, data: &[u8]) -> u64 { murmurs::murmur3_x64_128(data, 0)[0] } fn name(&self) -> &'static str { "murmur3" } } ``` -------------------------------- ### MetroHasher Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html MetroHasher is a hasher implementation using the MetroHash64 algorithm, known for its speed and quality. It is available when the `hash-metrohash` feature is enabled. ```APIDOC ## MetroHasher ### Description MetroHash hasher implementation (fast, high-quality). Available when the `hash-metrohash` feature is enabled. ### Methods #### new - **Signature**: `fn new() -> Self` - **Description**: Create a new MetroHasher instance. #### hash - **Signature**: `fn hash(&self, data: &[u8]) -> u64` - **Description**: Hash data using MetroHash64. #### name - **Signature**: `fn name(&self) -> &'static str` - **Description**: Returns the string `"metrohash"`. ``` -------------------------------- ### HashRing::resource_count Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Returns the number of resources in the ring. ```APIDOC ## HashRing::resource_count ### Description Returns the number of resources in the ring. ### Method resource_count ### Parameters None ### Returns - **usize** - The number of resources. ``` -------------------------------- ### RendezvousRing::get_weight Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Retrieves the weight of a resource if it exists in the ring. ```APIDOC ## RendezvousRing::get_weight ### Description Retrieves the weight of a resource if it exists in the ring. ### Method get_weight ### Parameters - **resource** (&dyn HasId) - Required - The resource to look up. ### Returns - **Option** - The weight of the resource, or None if not found. ``` -------------------------------- ### Test Rendezvous Hashing Consistency After Changes Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Checks that the majority of key mappings remain consistent after adding new resources to the ring. ```rust #[test] fn rendezvous_hash_consistency_after_changes() { let mut ring = RendezvousRing::new(); let resources = pick_some_fruit(); // Add initial resources for resource in resources.iter().take(3) { ring.add(Box::new(resource.clone())); } // Map some keys let mut initial_mappings = std::collections::HashMap::new(); for i in 0..50 { let key = format!("stable-key-{}", i); if let Some(located) = ring.locate(&key) { initial_mappings.insert(key, located.id().to_string()); } } // Add more resources ring.add(Box::new(resources[3].clone())); // Check that most keys still map to same resources let mut unchanged = 0; for (key, original_location) in &initial_mappings { if let Some(located) = ring.locate(key) && located.id() == original_location { unchanged += 1; } } // Some keys should remain unchanged, but rendezvous hashing redistributes more than consistent hashing // With 4 nodes, adding a 5th should cause about 20% redistribution assert!( unchanged > initial_mappings.len() / 2, "Too much redistribution: {}/{} keys remained unchanged", unchanged, initial_mappings.len() ); } ``` -------------------------------- ### RapidHashQualityHasher Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html RapidHashQualityHasher is a hasher implementation using the RapidHash quality variant, providing high-quality distribution. It is available when the `hash-rapidhash` feature is enabled. ```APIDOC ## RapidHashQualityHasher ### Description RapidHash quality hasher implementation (high-quality distribution). Available when the `hash-rapidhash` feature is enabled. ### Methods #### new - **Signature**: `fn new() -> Self` - **Description**: Create a new RapidHashQualityHasher instance. #### hash - **Signature**: `fn hash(&self, data: &[u8]) -> u64` - **Description**: Hash data using RapidHash quality variant. #### name - **Signature**: `fn name(&self) -> &'static str` - **Description**: Returns the string `"rapidhash-quality"`. ``` -------------------------------- ### Test rendezvous_hash_remove_resources Source: https://docs.rs/lightcycle/latest/src/lightcycle/rendezvous.rs.html Verifies that removing a resource decreases the count and that the removed resource is no longer returned by locate. ```rust #[test] fn rendezvous_hash_remove_resources() { let mut ring = RendezvousRing::new(); let resources = pick_some_fruit(); // Add all resources for resource in &resources { ring.add(Box::new(resource.clone())); } assert_eq!(ring.resource_count(), resources.len()); // Remove one resource let to_remove = &resources[0]; let boxed_remove: Box = Box::new(to_remove.clone()); ring.remove(&boxed_remove); assert_eq!(ring.resource_count(), resources.len() - 1); // Verify it's not returned by locate for i in 0..100 { let key = format!("key-{}", i); if let Some(located) = ring.locate(&key) { assert_ne!(located.id(), to_remove.id()); } } } ``` -------------------------------- ### HashRing::add_weighted Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Adds a new resource with a specific weight. For algorithms that don't support weighting, the weight is ignored and this behaves like `add()`. ```APIDOC ## HashRing::add_weighted ### Description Adds a new resource with a specific weight. For algorithms that don't support weighting, the weight is ignored and this behaves like `add()`. ### Method add_weighted ### Parameters - **resource** (Self::Resource) - Required - The resource to add. - **weight** (f64) - Required - The weight to assign. ### Returns None ``` -------------------------------- ### HashRing::is_empty Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Checks if the hash ring is empty. ```APIDOC ## HashRing::is_empty ### Description Checks if the hash ring is empty. ### Method is_empty ### Parameters None ### Returns - **bool** - True if the ring is empty, false otherwise. ``` -------------------------------- ### Single Node Ring Test Source: https://docs.rs/lightcycle/latest/src/lightcycle/consistent.rs.html Verifies that a ring with only one resource correctly maps all keys to that resource. ```rust #[test] fn single_node_rings() { let mut ring = ConsistentRing::new_with_replica_count(5); let durian = MockResource { name: "durian".to_string(), }; ring.add(Box::new(durian)); // nobody likes being next to durian let location = ring.locate("a").expect("everything should have a home of some kind"); assert_eq!(location.id(), "durian"); let location = ring.locate("z").expect("everything should have a home of some kind"); assert_eq!(location.id(), "durian"); } ``` -------------------------------- ### ConsistentHasher trait and Blake3Hasher implementation Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html Defines the ConsistentHasher trait with new(), hash(), and name() methods. The Blake3Hasher implementation is gated behind the hash-blake3 feature and converts the first 8 bytes of the Blake3 hash to a u64 using little-endian byte order. ```rust //! Hash function abstraction for consistent hashing //! //! This module provides a trait for hash functions and implementations //! for various hashing algorithms that can be selected via feature flags. /// Trait for hash functions used in consistent hashing pub trait ConsistentHasher: Clone + Send + Sync { /// Create a new hasher instance fn new() -> Self; /// Hash a byte slice and return a 64-bit integer for fast BTree operations fn hash(&self, data: &[u8]) -> u64; /// Get the name of this hasher for diagnostics fn name(&self) -> &'static str; } /// Blake3 hasher implementation (default) #[cfg(feature = "hash-blake3")] #[derive(Clone)] pub struct Blake3Hasher; #[cfg(feature = "hash-blake3")] impl ConsistentHasher for Blake3Hasher { fn new() -> Self { Blake3Hasher } fn hash(&self, data: &[u8]) -> u64 { // Convert first 8 bytes of blake3 hash to u64 for fast BTree operations let hash = blake3::hash(data); let bytes = hash.as_bytes(); u64::from_le_bytes([ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], ]) } fn name(&self) -> &'static str { "blake3" } } ``` -------------------------------- ### RapidHashFastHasher Source: https://docs.rs/lightcycle/latest/src/lightcycle/hashes.rs.html RapidHashFastHasher is a hasher implementation using the RapidHash fast variant, optimized for speed. It is available when the `hash-rapidhash` feature is enabled. ```APIDOC ## RapidHashFastHasher ### Description RapidHash fast hasher implementation (optimized for speed). Available when the `hash-rapidhash` feature is enabled. ### Methods #### new - **Signature**: `fn new() -> Self` - **Description**: Create a new RapidHashFastHasher instance. #### hash - **Signature**: `fn hash(&self, data: &[u8]) -> u64` - **Description**: Hash data using RapidHash fast variant. #### name - **Signature**: `fn name(&self) -> &'static str` - **Description**: Returns the string `"rapidhash-fast"`. ``` -------------------------------- ### RendezvousRing::hash_key Source: https://docs.rs/lightcycle/latest/lightcycle/struct.RendezvousRing.html Computes a raw hash value for a given key. Intended for testing purposes. ```APIDOC ## RendezvousRing::hash_key ### Description Computes a raw hash value for a given key. Intended for testing purposes. ### Method hash_key ### Parameters - **key** (&str) - Required - The key to hash. ### Returns - **u64** - The raw hash value. ```