Skip to content

Commit 006b597

Browse files
scsiguymeta-codesync[bot]
authored andcommitted
Fix OMP exception safety in IndexNNDescent and IndexNSG search (#5106)
Summary: Pull Request resolved: #5106 Apply the same OpenMP exception-safety fix from IndexFlatCodes::search to IndexNNDescent::search and IndexNSG::search. Both methods have identical OMP structure: a parallel region constructs per-thread VisitedTable and DistanceComputer objects, then a worksharing loop calls the graph search function. Exceptions thrown inside either the constructor or the loop body (e.g. from corrupt deserialized graph state) call std::terminate because OpenMP does not allow exceptions to escape worksharing constructs. Wrap the per-thread setup and the per-iteration loop body in try/catch blocks that capture exceptions via std::exception_ptr and re-throw on the main thread after the parallel region completes. Potential exception sources in the OMP region: - VisitedTable(ntotal): std::bad_alloc if ntotal is corrupt/huge. ntotal is validated as >= 0 during deserialization but has no upper bound. - storage_distance_computer(storage): may throw from get_distance_computer() if the storage index has corrupt state. - nndescent.search() / nsg.search(): throws FaissException if the graph has not been built (has_built check), or std::bad_alloc from internal vector allocations with corrupt search_L. Reviewed By: mnorris11 Differential Revision: D101031002
1 parent bc70373 commit 006b597

3 files changed

Lines changed: 97 additions & 18 deletions

File tree

faiss/IndexNNDescent.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <faiss/IndexNNDescent.h>
1111

12+
#include <atomic>
1213
#include <cinttypes>
1314
#include <cstdio>
1415
#include <cstdlib>
@@ -119,22 +120,36 @@ void IndexNNDescent::search(
119120
for (idx_t i0 = 0; i0 < n; i0 += check_period) {
120121
idx_t i1 = std::min(i0 + check_period, n);
121122

123+
std::exception_ptr ex;
124+
std::atomic<bool> interrupt{false};
122125
#pragma omp parallel
123126
{
124-
VisitedTable vt(ntotal);
125-
126-
std::unique_ptr<DistanceComputer> dis(
127-
storage_distance_computer(storage));
127+
std::unique_ptr<DistanceComputer> dis;
128+
std::unique_ptr<VisitedTable> vt;
129+
try {
130+
vt = std::make_unique<VisitedTable>(ntotal);
131+
dis.reset(storage_distance_computer(storage));
132+
} catch (...) {
133+
omp_capture_exception(ex, [&] { interrupt = true; });
134+
}
128135

129136
#pragma omp for
130137
for (idx_t i = i0; i < i1; i++) {
131-
idx_t* idxi = labels + i * k;
132-
float* simi = distances + i * k;
133-
dis->set_query(x + i * d);
134-
135-
nndescent.search(*dis, k, idxi, simi, vt);
138+
if (interrupt.load(std::memory_order_relaxed)) {
139+
continue;
140+
}
141+
try {
142+
idx_t* idxi = labels + i * k;
143+
float* simi = distances + i * k;
144+
dis->set_query(x + i * d);
145+
146+
nndescent.search(*dis, k, idxi, simi, *vt);
147+
} catch (...) {
148+
omp_capture_exception(ex, [&] { interrupt = true; });
149+
}
136150
}
137151
}
152+
omp_rethrow_if_exception(ex);
138153
InterruptCallback::check();
139154
}
140155

faiss/IndexNSG.cpp

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <faiss/IndexNSG.h>
1111

12+
#include <atomic>
1213
#include <cinttypes>
1314
#include <memory>
1415

@@ -74,24 +75,39 @@ void IndexNSG::search(
7475
for (idx_t i0 = 0; i0 < n; i0 += check_period) {
7576
idx_t i1 = std::min(i0 + check_period, n);
7677

78+
std::exception_ptr ex;
79+
std::atomic<bool> interrupt{false};
7780
#pragma omp parallel
7881
{
79-
VisitedTable vt(ntotal, nsg.use_visited_hashset);
80-
81-
std::unique_ptr<DistanceComputer> dis(
82-
storage_distance_computer(storage));
82+
std::unique_ptr<DistanceComputer> dis;
83+
std::unique_ptr<VisitedTable> vt;
84+
try {
85+
vt = std::make_unique<VisitedTable>(
86+
ntotal, nsg.use_visited_hashset);
87+
dis.reset(storage_distance_computer(storage));
88+
} catch (...) {
89+
omp_capture_exception(ex, [&] { interrupt = true; });
90+
}
8391

8492
#pragma omp for
8593
for (idx_t i = i0; i < i1; i++) {
86-
idx_t* idxi = labels + i * k;
87-
float* simi = distances + i * k;
88-
dis->set_query(x + i * d);
94+
if (interrupt.load(std::memory_order_relaxed)) {
95+
continue;
96+
}
97+
try {
98+
idx_t* idxi = labels + i * k;
99+
float* simi = distances + i * k;
100+
dis->set_query(x + i * d);
89101

90-
nsg.search(*dis, k, idxi, simi, vt);
102+
nsg.search(*dis, k, idxi, simi, *vt);
91103

92-
vt.advance();
104+
vt->advance();
105+
} catch (...) {
106+
omp_capture_exception(ex, [&] { interrupt = true; });
107+
}
93108
}
94109
}
110+
omp_rethrow_if_exception(ex);
95111
InterruptCallback::check();
96112
}
97113

tests/test_omp_exception_safety.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <faiss/IndexFlat.h>
1717
#include <faiss/IndexFlatCodes.h>
1818
#include <faiss/IndexIVFFlat.h>
19+
#include <faiss/IndexNNDescent.h>
20+
#include <faiss/IndexNSG.h>
1921
#include <faiss/impl/AuxIndexStructures.h>
2022
#include <faiss/impl/FaissException.h>
2123
#include <faiss/invlists/InvertedLists.h>
@@ -333,3 +335,49 @@ TEST(OMPExceptionSafety, flatcodes_search) {
333335
index.search(1, xq.data(), 1, distances.data(), labels.data()),
334336
std::runtime_error);
335337
}
338+
339+
// ---------------------------------------------------------------------------
340+
// IndexNNDescent::search: exception in OMP worker propagates to caller.
341+
// Constructing with has_built=false triggers a FaissException inside the
342+
// worksharing loop body.
343+
// ---------------------------------------------------------------------------
344+
TEST(OMPExceptionSafety, nndescent_search) {
345+
int d = 4;
346+
auto storage = std::make_unique<IndexFlatL2>(d);
347+
std::vector<float> xb(d, 1.0f);
348+
storage->add(1, xb.data());
349+
350+
IndexNNDescent index(storage.get(), 4);
351+
index.ntotal = 1;
352+
// has_built defaults to false, so nndescent.search() will throw.
353+
354+
std::vector<float> xq(d, 0.0f);
355+
std::vector<float> distances(1);
356+
std::vector<idx_t> labels(1);
357+
358+
EXPECT_THROW(
359+
index.search(1, xq.data(), 1, distances.data(), labels.data()),
360+
FaissException);
361+
}
362+
363+
// ---------------------------------------------------------------------------
364+
// IndexNSG::search: exception in OMP worker propagates to caller
365+
// ---------------------------------------------------------------------------
366+
TEST(OMPExceptionSafety, nsg_search) {
367+
int d = 4;
368+
auto storage = std::make_unique<IndexFlatL2>(d);
369+
std::vector<float> xb(d, 1.0f);
370+
storage->add(1, xb.data());
371+
372+
IndexNSG index(storage.get(), 4);
373+
index.ntotal = 1;
374+
// nsg graph is not built, so nsg.search() will throw.
375+
376+
std::vector<float> xq(d, 0.0f);
377+
std::vector<float> distances(1);
378+
std::vector<idx_t> labels(1);
379+
380+
EXPECT_THROW(
381+
index.search(1, xq.data(), 1, distances.data(), labels.data()),
382+
FaissException);
383+
}

0 commit comments

Comments
 (0)