From cfe4e5c760d523ae3e501536169f211e445d7a9a Mon Sep 17 00:00:00 2001 From: Bruno Volpato Date: Sun, 19 Jul 2026 14:43:26 -0400 Subject: [PATCH] Fix: Keep reserve capacity exact and monotonic --- include/usearch/index_dense.hpp | 9 +++++---- rust/lib.rs | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/include/usearch/index_dense.hpp b/include/usearch/index_dense.hpp index b0d6766a..57dd475f 100644 --- a/include/usearch/index_dense.hpp +++ b/include/usearch/index_dense.hpp @@ -1050,17 +1050,18 @@ class index_dense_gt { */ bool try_reserve(index_limits_t limits) { + // Reserving is monotonic: reducing thread limits must not shrink member-addressed storage. + limits.members = (std::max)(limits.members, typed_->capacity()); + limits.members = (std::max)(limits.members, vectors_lookup_.size()); + // The slot lookup system will generally prefer power-of-two sizes. if (config_.enable_key_lookups) { unique_lock_t lock(slot_lookup_mutex_); if (!slot_lookup_.try_reserve(limits.members)) return false; - limits.members = slot_lookup_.capacity(); } - // Once the `slot_lookup_` grows, let's use its capacity as the new - // target for the `vectors_lookup_` to synchronize allocations and - // expensive index re-organizations. + // Hash-table load-factor slack does not need corresponding vector or graph slots. if (limits.members != vectors_lookup_.size()) { vectors_lookup_t new_vectors_lookup(limits.members); if (!new_vectors_lookup) diff --git a/rust/lib.rs b/rust/lib.rs index 4bac26fc..6c6e3304 100644 --- a/rust/lib.rs +++ b/rust/lib.rs @@ -2200,6 +2200,28 @@ mod tests { assert_eq!(index.size(), 0); } + #[test] + fn reserve_uses_requested_member_capacity_and_never_shrinks() { + let options = IndexOptions { + dimensions: 4, + metric: MetricKind::IP, + quantization: ScalarKind::F32, + ..Default::default() + }; + let index = Index::new(&options).unwrap(); + + index.reserve(10).unwrap(); + assert_eq!(index.capacity(), 10); + + index.reserve(4).unwrap(); + assert_eq!(index.capacity(), 10); + + for key in 0..10 { + index.add(key, &[key as f32, 0.0, 0.0, 0.0]).unwrap(); + } + assert_eq!(index.size(), 10); + } + #[test] fn stats_variants() { let options = IndexOptions {