diff --git a/faiss/IndexHNSW.cpp b/faiss/IndexHNSW.cpp index d295a3dd4..424bf7dde 100644 --- a/faiss/IndexHNSW.cpp +++ b/faiss/IndexHNSW.cpp @@ -475,6 +475,9 @@ void hnsw_search( rq->stats.reset(); } + // HNSW::search owns the per-query generation advance. The + // reusable-table acquisition above only clears stale TLS + // state before this thread starts processing queries. HNSWStats stats = hnsw.search(*dis, index, *res, *vt, params); n1 += stats.n1; @@ -486,7 +489,6 @@ void hnsw_search( n_rabitq_refine += rq->stats.n_refine; } res->end(); - vt->advance(); } catch (...) { omp_capture_exception(ex, [&] { interrupt = true; }); } diff --git a/tests/test_hnsw.cpp b/tests/test_hnsw.cpp index 163ccadce..4ff6b7ab2 100644 --- a/tests/test_hnsw.cpp +++ b/tests/test_hnsw.cpp @@ -848,6 +848,26 @@ struct ScopedOmpThreads { } }; +TEST_F(HNSWTest, TEST_search_advances_one_generation_per_query) { + ScopedOmpThreads omp_guard; + omp_set_num_threads(1); + index->hnsw.use_visited_hashset = false; + + auto& reusable = dynamic_cast( + faiss::VisitedTable::get_reusable(index->ntotal, false)); + std::fill(reusable.visited.begin(), reusable.visited.end(), 0); + reusable.visno = 10; + + std::vector labels(k); + std::vector distances(k); + index->search(1, xq->data(), k, distances.data(), labels.data()); + + // Acquiring the reusable table advances 10 -> 11 to clear stale state. + // HNSW::search then advances 11 -> 12 after the query. The outer wrapper + // must not perform a third advance. + EXPECT_EQ(12, reusable.visno); +} + TEST_F(HNSWTest, TEST_search_reuse_correctness) { ScopedOmpThreads omp_guard;