prepareGeneratedDefaultMap() {
defaultMap.put(FessConfig.RANK_FUSION_window_size, "200");
defaultMap.put(FessConfig.RANK_FUSION_rank_constant, "20");
defaultMap.put(FessConfig.RANK_FUSION_THREADS, "-1");
+ defaultMap.put(FessConfig.RANK_FUSION_TIMEOUT, "10000");
defaultMap.put(FessConfig.RANK_FUSION_score_field, "rf_score");
defaultMap.put(FessConfig.SMB_ROLE_FROM_FILE, "true");
defaultMap.put(FessConfig.SMB_AVAILABLE_SID_TYPES, "1,2,4:2,5:1");
diff --git a/src/main/java/org/codelibs/fess/rank/fusion/RankFusionProcessor.java b/src/main/java/org/codelibs/fess/rank/fusion/RankFusionProcessor.java
index 56aca41b8..098b3a056 100644
--- a/src/main/java/org/codelibs/fess/rank/fusion/RankFusionProcessor.java
+++ b/src/main/java/org/codelibs/fess/rank/fusion/RankFusionProcessor.java
@@ -32,6 +32,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
@@ -325,6 +326,12 @@ protected RankFusionSearcher[] getAvailableSearchers() {
* Executes searches in parallel across all provided searchers, then combines the results
* using rank fusion algorithms to produce a unified result set.
*
+ * The main searcher runs on the calling thread and is always waited for. The other searchers
+ * run on the executor and are waited for up to {@code rank.fusion.timeout} milliseconds, counted
+ * from their submission. One that has not answered by then - an embedding provider that accepted
+ * the query and never replied, for instance - is left out of this search, and the response is
+ * flagged as partial and timed out.
+ *
* @param searchers array of searchers to use for concurrent searching
* @param query the search query string
* @param params search request parameters including pagination and filters
@@ -380,9 +387,12 @@ protected List