github-actions[bot] commented on code in PR #67024:
URL: https://github.com/apache/doris/pull/67024#discussion_r3840364110


##########
be/test/storage/index/ann/faiss_vector_index_test.cpp:
##########
@@ -1465,6 +1466,81 @@ TEST_F(VectorSearchTest, IVFOnDiskSaveLoadAndSearch) {
     EXPECT_GT(result.ivf_on_disk_cache_miss_cnt, 0);
 }
 
+// Searching WITHOUT a list cache has to read, not recurse.
+//
+// faiss documents RandomAccessReader::borrow()'s default as "allocates a 
buffer
+// and calls read_at()", and CachedRandomAccessReader::read_at() is 
implemented by
+// calling borrow(). Falling back to the base borrow() when
+// AnnIndexIVFListCache::instance() is null is therefore unbounded mutual
+// recursion. Every other IVF-on-disk test above installs the cache first, 
which
+// is exactly why nothing has ever hit it.
+//
+// exec_env_init installs the cache unconditionally at BE startup, so a 
running BE
+// does not reach this today. It is a trap for any unit test that does not 
know to
+// install one -- it does not fail, it never returns -- and for any change that
+// makes the cache optional.
+//
+// The search runs on a worker with a deadline because before the fix it does 
not
+// return at all: called inline it would hang the whole suite instead of 
failing
+// this one case. Everything the worker touches is shared-owned so a hung 
thread
+// cannot outlive its arguments.
+TEST_F(VectorSearchTest, IVFOnDiskSearchWithoutTheListCacheStillReads) {
+    ASSERT_EQ(AnnIndexIVFListCache::instance(), nullptr)
+            << "this case is about the no-cache path, but something installed 
a cache";
+
+    auto builder = std::make_unique<FaissVectorIndex>();
+    FaissBuildParameter params;
+    params.dim = 32;
+    params.ivf_nlist = 4;
+    params.quantizer = FaissBuildParameter::Quantizer::FLAT;
+    params.index_type = FaissBuildParameter::IndexType::IVF_ON_DISK;
+    builder->build(params);
+
+    const int num_vectors = 200;
+    std::vector<float> vectors;
+    vectors.reserve(static_cast<size_t>(num_vectors) * params.dim);
+    for (int i = 0; i < num_vectors; i++) {
+        auto tmp = vector_search_utils::generate_random_vector(params.dim);
+        vectors.insert(vectors.end(), tmp.begin(), tmp.end());
+    }
+    ASSERT_TRUE(builder->train(num_vectors, vectors.data()).ok());
+    ASSERT_TRUE(builder->add(num_vectors, vectors.data()).ok());
+
+    auto dir = std::make_shared<lucene::store::RAMDirectory>();
+    ASSERT_TRUE(builder->save(dir.get()).ok());
+
+    auto index = std::make_shared<FaissVectorIndex>();
+    index->set_type(AnnIndexType::IVF_ON_DISK);
+    index->set_ivfdata_cache_key_prefix("ut_no_list_cache");
+    ASSERT_TRUE(index->load(dir.get()).ok());
+
+    auto query_vec = std::make_shared<std::vector<float>>(
+            vector_search_utils::generate_random_vector(params.dim));
+    auto roaring = std::make_shared<roaring::Roaring>();
+    for (int i = 0; i < num_vectors; ++i) {
+        roaring->add(i);
+    }
+    auto result = std::make_shared<IndexSearchResult>();
+
+    auto done = std::make_shared<std::promise<Status>>();
+    auto ready = done->get_future();
+    std::thread worker([index, dir, query_vec, roaring, result, done, 
num_vectors] {
+        IVFSearchParameters search_params;
+        search_params.nprobe = 4;
+        search_params.roaring = roaring.get();
+        search_params.rows_of_segment = num_vectors;
+        done->set_value(index->ann_topn_search(query_vec->data(), 10, 
search_params, *result));
+    });
+    const bool returned = ready.wait_for(std::chrono::seconds(60)) == 
std::future_status::ready;
+    if (!returned) {
+        worker.detach();

Review Comment:
   [P1] Isolate the non-returning regression instead of detaching it
   
   This deadline does not make the failure safe. The old recursion allocates on 
every frame and can stack-overflow/OOM before 60 seconds; for a hang that does 
reach this branch, `FAIL()` returns after `detach()` while the worker continues 
reading the raw `AnnIndexIVFListCache` singleton. The immediately following 
test creates and destroys that singleton, so the leaked search can race it, and 
any exception escaping the worker calls `std::terminate` before the promise is 
satisfied. Please run the potentially non-returning call in a killable 
subprocess (or expose a bounded/direct reader seam) so timeout/crash is 
isolated and the worker is never left in the shared test process.



##########
be/test/storage/index/ann/faiss_vector_index_test.cpp:
##########
@@ -1465,6 +1466,81 @@ TEST_F(VectorSearchTest, IVFOnDiskSaveLoadAndSearch) {
     EXPECT_GT(result.ivf_on_disk_cache_miss_cnt, 0);
 }
 
+// Searching WITHOUT a list cache has to read, not recurse.
+//
+// faiss documents RandomAccessReader::borrow()'s default as "allocates a 
buffer
+// and calls read_at()", and CachedRandomAccessReader::read_at() is 
implemented by
+// calling borrow(). Falling back to the base borrow() when
+// AnnIndexIVFListCache::instance() is null is therefore unbounded mutual
+// recursion. Every other IVF-on-disk test above installs the cache first, 
which
+// is exactly why nothing has ever hit it.
+//
+// exec_env_init installs the cache unconditionally at BE startup, so a 
running BE
+// does not reach this today. It is a trap for any unit test that does not 
know to
+// install one -- it does not fail, it never returns -- and for any change that
+// makes the cache optional.
+//
+// The search runs on a worker with a deadline because before the fix it does 
not
+// return at all: called inline it would hang the whole suite instead of 
failing
+// this one case. Everything the worker touches is shared-owned so a hung 
thread
+// cannot outlive its arguments.
+TEST_F(VectorSearchTest, IVFOnDiskSearchWithoutTheListCacheStillReads) {
+    ASSERT_EQ(AnnIndexIVFListCache::instance(), nullptr)
+            << "this case is about the no-cache path, but something installed 
a cache";
+
+    auto builder = std::make_unique<FaissVectorIndex>();
+    FaissBuildParameter params;
+    params.dim = 32;
+    params.ivf_nlist = 4;
+    params.quantizer = FaissBuildParameter::Quantizer::FLAT;
+    params.index_type = FaissBuildParameter::IndexType::IVF_ON_DISK;
+    builder->build(params);
+
+    const int num_vectors = 200;
+    std::vector<float> vectors;
+    vectors.reserve(static_cast<size_t>(num_vectors) * params.dim);
+    for (int i = 0; i < num_vectors; i++) {
+        auto tmp = vector_search_utils::generate_random_vector(params.dim);
+        vectors.insert(vectors.end(), tmp.begin(), tmp.end());
+    }
+    ASSERT_TRUE(builder->train(num_vectors, vectors.data()).ok());
+    ASSERT_TRUE(builder->add(num_vectors, vectors.data()).ok());
+
+    auto dir = std::make_shared<lucene::store::RAMDirectory>();
+    ASSERT_TRUE(builder->save(dir.get()).ok());
+
+    auto index = std::make_shared<FaissVectorIndex>();
+    index->set_type(AnnIndexType::IVF_ON_DISK);
+    index->set_ivfdata_cache_key_prefix("ut_no_list_cache");
+    ASSERT_TRUE(index->load(dir.get()).ok());
+
+    auto query_vec = std::make_shared<std::vector<float>>(
+            vector_search_utils::generate_random_vector(params.dim));
+    auto roaring = std::make_shared<roaring::Roaring>();
+    for (int i = 0; i < num_vectors; ++i) {
+        roaring->add(i);
+    }
+    auto result = std::make_shared<IndexSearchResult>();
+
+    auto done = std::make_shared<std::promise<Status>>();
+    auto ready = done->get_future();
+    std::thread worker([index, dir, query_vec, roaring, result, done, 
num_vectors] {

Review Comment:
   [P2] Initialize the Doris thread context in this worker
   
   `run_all_tests.cpp` initializes `ThreadContext` only on the main test 
thread, and TLS is not inherited by this new `std::thread`. This lambda enters 
the full FAISS/CLucene search path without `SCOPED_INIT_THREAD_CONTEXT()`, 
contrary to the BE runtime thread-entry invariant; allocator-aware work on this 
path would otherwise be charged as orphan/unattached memory. Please include 
`runtime/thread_context.h` and initialize the context at the start of the 
worker, as the other raw-thread storage tests do.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to