-
Notifications
You must be signed in to change notification settings - Fork 295
Description
Describe the bug
When performing concurrent add and remove operations, the Index::size() method can return an extremely large value (e.g., 18446744073709551614).
This occurs because the underlying C++ implementation calculates size as: return typed_->size() - free_keys_.size();.
Under high concurrency, it is possible for the free_keys_ size to be larger than the typed_ size, causing a std::size_t underflow.
Debugging reveals the following state during the crash:
typed_->size(): 88free_keys_.size(): 91- Result: 88 - 91 = -3 -> 18446744073709551613 (approx).
Steps to reproduce
This issue was reproduced using the Rust bindings with the tokio multi-threaded runtime. Note that even when using index.read() (assuming internal thread-safety of usearch), the inconsistency between the two internal counters is exposed.
#[tokio::test(flavor = "multi_thread")]
async fn test_concurrent_usearch_add_remove_size() {
let options = IndexOptions {
dimensions: 3,
..Default::default()
};
let index = Arc::new(RwLock::new(
usearch::Index::new(&options).expect("failed to create index"),
));
const VECTORS: usize = 50;
let threads = tokio::runtime::Handle::current().metrics().num_workers();
index.write().unwrap().reserve(VECTORS * 10).expect("failed to reserve");
let mut handles = vec![];
for _ in 0..threads {
let idx = Arc::clone(&index);
// Task 1: Concurrent Adds
handles.push(tokio::spawn(async move {
for i in 0..VECTORS {
let _ = idx.read().unwrap().add(i as u64, &[i as f32; 3]);
}
}));
// Task 2: Concurrent Removes
let idx = Arc::clone(&index);
handles.push(tokio::spawn(async move {
for i in 0..VECTORS {
let _ = idx.read().unwrap().remove(i as u64);
}
}));
// Task 3: Monitor Size
let idx = Arc::clone(&index);
handles.push(tokio::spawn(async move {
for _ in 0..VECTORS * 10 {
let size = idx.read().unwrap().size();
assert!(size <= VECTORS * threads, "Underflow detected! Size: {}", size);
tokio::task::yield_now().await;
}
}));
}
for handle in handles {
handle.await.expect("Task failed");
}
}Expected behavior
Index::size() to always return valid number of vectors inside the index.
USearch version
2.23.0
Operating System
Fedora release 42 (Adams)
Hardware architecture
x86
Which interface are you using?
Other bindings
Contact Details
No response
Are you open to being tagged as a contributor?
- I am open to being mentioned in the project
.githistory as a contributor
Is there an existing issue for this?
- I have searched the existing issues
Code of Conduct
- I agree to follow this project's Code of Conduct