From d6606e2cf80e76161b71aeecca2bce69b01e29c4 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Thu, 27 Aug 2026 20:51:37 -0700 Subject: [PATCH] fts: serialize concurrent mutations of QFTSEdgeCompute scores map QFTSEdgeCompute copies share the same scores map by reference (one copy per worker thread in a scheduled parallel frontier task), but edgeCompute mutated the map with no synchronization. Concurrent emplace/at() can rehash the map while other threads mutate it, corrupting the heap. Guard all mutations with a shared mutex, mirroring the existing MatchTermsVertexCompute::resDfsMutex pattern. Related to LadybugDB/ladybug#840. --- fts/src/function/query_fts_index.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/fts/src/function/query_fts_index.cpp b/fts/src/function/query_fts_index.cpp index aaf3435d..1c007e7e 100644 --- a/fts/src/function/query_fts_index.cpp +++ b/fts/src/function/query_fts_index.cpp @@ -1,5 +1,6 @@ #include "function/query_fts_index.h" +#include #include #include "binder/binder.h" @@ -131,10 +132,18 @@ struct ScoreInfo { struct QFTSEdgeCompute final : EdgeCompute { node_id_map_t& scores; const std::unordered_map& dfs; + // The scores map is shared by reference between copies of this edge compute (one per + // worker thread in a parallel frontier task), so all mutations must be serialized. + std::shared_ptr scoresMutex; QFTSEdgeCompute(node_id_map_t& scores, const std::unordered_map& dfs) - : scores{scores}, dfs{dfs} {} + : scores{scores}, dfs{dfs}, scoresMutex{std::make_shared()} {} + + QFTSEdgeCompute(node_id_map_t& scores, + const std::unordered_map& dfs, + std::shared_ptr scoresMutex) + : scores{scores}, dfs{dfs}, scoresMutex{std::move(scoresMutex)} {} std::vector edgeCompute(nodeID_t boundNodeID, graph::NbrScanState::Chunk& resultChunk, bool) override { @@ -143,6 +152,7 @@ struct QFTSEdgeCompute final : EdgeCompute { std::vector activeNodes; resultChunk.forEach([&](auto neighbors, auto propertyVectors, auto i) { auto docNodeID = neighbors[i]; + std::lock_guard guard{*scoresMutex}; if (!scores.contains(docNodeID)) { scores.emplace(docNodeID, ScoreInfo{}); } @@ -154,7 +164,7 @@ struct QFTSEdgeCompute final : EdgeCompute { } std::unique_ptr copy() override { - return std::make_unique(scores, dfs); + return std::make_unique(scores, dfs, scoresMutex); } };