Skip to content

Commit

Permalink
Fix: Dead-lock on post-removal insertions
Browse files Browse the repository at this point in the history
Closes #175
  • Loading branch information
ashvardanian committed Jul 31, 2023
1 parent 7743709 commit 284b058
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 12 deletions.
10 changes: 10 additions & 0 deletions cpp/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
expect((count == 3));
expect((index.stats(0).nodes == 3));

// Try removals and replacements
if constexpr (punned_ak) {
using labeling_result_t = typename index_t::labeling_result_t;
labeling_result_t result = index.remove(key_third);
expect(bool(result));
expect(index.size() == 2);
index.add(key_third, vector_third, args...);
expect(index.size() == 3);
}

// Search again over reconstructed index
index.save("tmp.usearch");
index.load("tmp.usearch");
Expand Down
21 changes: 15 additions & 6 deletions include/usearch/index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2093,13 +2093,18 @@ class index_gt {
node_lock_t new_lock = node_lock_(old_slot);
node_t node = node_at_(old_slot);

level_t node_level = node.level();
span_bytes_t node_bytes = node_bytes_(node);
std::memset(node_bytes.data(), 0, node_bytes.size());
node.level(node_level);

// Pull stats
result.measurements = context.measurements_count;
result.cycles = context.iteration_cycles;

connect_node_across_levels_( //
old_slot, value, metric, //
entry_slot_, max_level_, node.level(), //
connect_node_across_levels_( //
old_slot, value, metric, //
entry_slot_, max_level_, node_level, //
config, context);
node.key(key);

Expand Down Expand Up @@ -2535,7 +2540,7 @@ class index_gt {
// From `target_level` down perform proper extensive search
for (level_t level = (std::min)(target_level, max_level); level >= 0; --level) {
// TODO: Handle out of memory conditions
search_to_insert_(closest_slot, value, metric, level, config.expansion, context);
search_to_insert_(closest_slot, node_slot, value, metric, level, config.expansion, context);
closest_slot = connect_new_node_(node_slot, level, context, metric);
reconnect_neighbor_nodes_(node_slot, value, level, context, metric);
}
Expand Down Expand Up @@ -2576,6 +2581,8 @@ class index_gt {
// Reverse links from the neighbors:
std::size_t const connectivity_max = level ? config_.connectivity : config_.connectivity_base;
for (compressed_slot_t close_slot : new_neighbors) {
if (close_slot == new_slot)
continue;
node_lock_t close_lock = node_lock_(close_slot);
node_t close_node = node_at_(close_slot);

Expand Down Expand Up @@ -2647,8 +2654,8 @@ class index_gt {
* @return `true` if procedure succeeded, `false` if run out of memory.
*/
template <typename value_at, typename metric_at>
bool search_to_insert_( //
std::size_t start_slot, value_at&& query, metric_at&& metric, //
bool search_to_insert_( //
std::size_t start_slot, std::size_t new_slot, value_at&& query, metric_at&& metric, //
level_t level, std::size_t top_limit, context_t& context) noexcept {

visits_bitset_t& visits = context.visits;
Expand All @@ -2674,6 +2681,8 @@ class index_gt {
context.iteration_cycles++;

compressed_slot_t candidate_slot = candidacy.slot;
if (new_slot == candidate_slot)
continue;
node_t candidate_ref = node_at_(candidate_slot);
node_lock_t candidate_lock = node_lock_(candidate_slot);
neighbors_ref_t candidate_neighbors = neighbors_(candidate_ref, level);
Expand Down
7 changes: 1 addition & 6 deletions include/usearch/index_dense.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1165,13 +1165,8 @@ class index_dense_gt {
unique_lock_t slot_lock(slot_lookup_mutex_);
slot_lookup_.insert({key, static_cast<compressed_slot_t>(member.slot)});
if (copy_vector) {
if (!reuse_node) {
// std::size_t result_size = metric_.bytes_per_vector();
// byte_t* result = vectors_allocator_.allocate(result_size);
// std::printf("alloc %zu size %zu dims %zu \n", (std::size_t)result, result_size,
// metric_.dimensions());
if (!reuse_node)
vectors_lookup_[member.slot] = vectors_allocator_.allocate(metric_.bytes_per_vector());
}
std::memcpy(vectors_lookup_[member.slot], vector_data, metric_.bytes_per_vector());
} else
vectors_lookup_[member.slot] = (byte_t*)vector_data;
Expand Down
6 changes: 6 additions & 0 deletions python/scripts/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ def test_index(
index.remove(43)
assert len(index) == 1

# Try insreting back
index.add(43, other_vector)
assert len(index) == 2
index.remove(43)
assert len(index) == 1

index.save(temporary_usearch_filename)

# Re-populate cleared index
Expand Down

0 comments on commit 284b058

Please sign in to comment.