Skip to content

Commit 425836f

Browse files
marevolclaude
andauthored
Improve rank fusion implementation: thread safety, error handling, and code quality (#2955)
* Improve Rank Fusion implementation This commit enhances the rank fusion implementation with several improvements: 1. Fix typo: Rename setSeacher() to setSearcher() - Updated method name in RankFusionProcessor - Updated DI configuration in fess_rankfusion.xml - Updated all test usages in RankFusionProcessorTest 2. Improve thread safety - Replace array-based searchers with CopyOnWriteArrayList - Eliminates race conditions during searcher registration - Removes inefficient Arrays.copyOf() pattern 3. Enhance error handling and logging - Separate InterruptedException and ExecutionException handling - Restore interrupt status when interrupted - Log actual exception causes for better debugging - Add more descriptive debug messages throughout 4. Improve code readability - Replace ambiguous lambda parameters (n, i, j) with descriptive names - Add inline comments explaining RRF score calculation - Improve variable names (scoreDocMap -> documentsByIdMap, etc.) - Clarify window size validation logic - Use method references instead of lambda expressions where applicable These changes maintain backward compatibility while improving code quality, maintainability, and thread safety of the rank fusion system. * Improve log message clarity in RankFusionProcessor - Change "minimum recommended size" to "required minimum size" in warning message, as the code enforces this as a hard requirement - Change "using first searcher" to "falling back to default searcher" for better clarity about which searcher is being used * Add comprehensive test suite for rank fusion improvements Add extensive test coverage for the rank fusion implementation improvements: 1. RankFusionProcessorConcurrencyTest.java - Test concurrent searcher registration from multiple threads - Test search operations during searcher registration - Test concurrent setSearcher and register operations - Test multiple concurrent search operations - Verifies CopyOnWriteArrayList thread safety improvements 2. RankFusionProcessorEdgeCaseTest.java - Test empty searcher list handling - Test setSearcher on empty and non-empty lists - Test zero and very large page sizes - Test search beyond available documents - Test negative start positions - Test searchers returning no/single/mixed results - Test null and empty query strings - Test duplicate document IDs across searchers - Test multiple close() calls 3. RankFusionProcessorErrorHandlingTest.java - Test handling of searchers throwing exceptions - Test documents with missing/null/non-string ID fields - Test all searchers failing gracefully - Test slow searcher handling (timeout scenarios) - Verifies improved error handling with separate exception types 4. SearchResultTest.java - Test SearchResult builder pattern - Test default values and various field combinations - Test document list operations - Test immutability and builder reuse 5. RankFusionSearcherTest.java - Test getName() derivation from class name - Test name caching and inheritance - Test abstract search method implementation These tests provide comprehensive coverage of: - Thread safety improvements (CopyOnWriteArrayList) - Error handling improvements (InterruptedException, ExecutionException) - Edge cases and boundary conditions - Builder pattern correctness - API contract verification Total test methods added: 40+ * Fix NullPointerException in rank fusion tests 1. RankFusionProcessor.java (line 309) - Add null check before accessing document fields - Prevents NPE when searcher returns null documents - Change: if (doc.get(idField)) -> if (doc != null && doc.get(idField)) - Fixes: RankFusionProcessorErrorHandlingTest.test_searcherReturnsNullDocuments 2. FacetResponse.java (line 55) - Add null check for aggregations parameter in constructor - Allows FacetResponse to be created with null aggregations - Update JavaDoc to document null parameter support - Fixes: SearchResultTest.test_searchResultWithFacetResponse - Fixes: SearchResultTest.test_allFieldsPopulated These defensive checks improve robustness when handling edge cases in error scenarios and test conditions. * Fix ArithmeticException and RuntimeException in rank fusion tests This commit addresses test failures in RankFusionProcessorEdgeCaseTest and RankFusionProcessorErrorHandlingTest: 1. Fix ArithmeticException: / by zero in test_emptySearcherList - Add check in search() method for empty searcher array - Return empty result list when no searchers are available - Add defensive check in searchWithMultipleSearchers() to prevent division by zero: windowSize / searchers.length - Fixes: RankFusionProcessorEdgeCaseTest.test_emptySearcherList 2. Fix RuntimeException in test_allSearchersFail - Wrap searcher.search() call in try-catch in searchWithMainSearcher() - Return empty result when main searcher throws exception - Log error with full exception details for debugging - Fixes: RankFusionProcessorErrorHandlingTest.test_allSearchersFail These defensive programming improvements ensure graceful degradation: - Empty searcher lists return empty results instead of crashing - Failed searchers are logged and handled without propagating exceptions - Consistent empty result format across all failure scenarios All fixes maintain backward compatibility and improve system robustness when dealing with edge cases and error conditions. * Change log level from error to warn for search failures Adjust logging severity for non-critical search operation failures: 1. ExecutionException in concurrent search operations (line 303) - Change: logger.error -> logger.warn - Reason: Individual searcher failures don't stop the system 2. Main searcher exceptions in searchWithMainSearcher (line 421) - Change: logger.error -> logger.warn - Reason: Search failures are recoverable, return empty results Rationale: - logger.error should be reserved for critical system failures - Search operation failures are recoverable and don't require system shutdown - These scenarios gracefully degrade by returning empty results - warn level is appropriate for operational issues that don't prevent the system from continuing to function This change improves log clarity by distinguishing between recoverable search failures (warn) and critical system errors (error). --------- Co-authored-by: Claude <[email protected]>
1 parent f02e284 commit 425836f

File tree

10 files changed

+1613
-572
lines changed

10 files changed

+1613
-572
lines changed

src/main/java/org/codelibs/fess/rank/fusion/DefaultSearcher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ protected SearchResult processResponse(final OptionalEntity<SearchResponse> sear
117117
docMap.put(fessConfig.getQueryCollapseInnerHitsName() + "_hash", bitsField.getValues().get(0));
118118
}
119119
docMap.put(fessConfig.getQueryCollapseInnerHitsName(), StreamUtil.stream(innerSearchHits.getHits())
120-
.get(stream -> stream.map(v -> parseSearchHit(fessConfig, hlPrefix, v)).toArray(n -> new Map[n])));
120+
.get(stream -> stream.map(hit -> parseSearchHit(fessConfig, hlPrefix, hit)).toArray(Map[]::new)));
121121
}
122122
}
123123
}
@@ -227,7 +227,7 @@ protected Map<String, Object> parseSearchHit(final FessConfig fessConfig, final
227227
});
228228
if (Constants.TEXT_FRAGMENT_TYPE_HIGHLIGHT.equals(fessConfig.getQueryHighlightTextFragmentType())) {
229229
docMap.put(Constants.TEXT_FRAGMENTS,
230-
viewHelper.createTextFragmentsByHighlight(highlightFields.values().toArray(n -> new HighlightField[n])));
230+
viewHelper.createTextFragmentsByHighlight(highlightFields.values().toArray(HighlightField[]::new)));
231231
}
232232
}
233233
} catch (final Exception e) {

src/main/java/org/codelibs/fess/rank/fusion/RankFusionProcessor.java

Lines changed: 103 additions & 60 deletions
Large diffs are not rendered by default.

src/main/java/org/codelibs/fess/util/FacetResponse.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,22 @@ public class FacetResponse {
4949
* Constructs a FacetResponse from OpenSearch aggregations.
5050
* Processes both field facets and query facets from the aggregation results.
5151
*
52-
* @param aggregations the OpenSearch aggregations containing facet data
52+
* @param aggregations the OpenSearch aggregations containing facet data, may be null
5353
*/
5454
public FacetResponse(final Aggregations aggregations) {
55-
aggregations.forEach(aggregation -> {
56-
if (aggregation.getName().startsWith(Constants.FACET_FIELD_PREFIX)) {
57-
final Terms termFacet = (Terms) aggregation;
58-
fieldList.add(new Field(termFacet));
59-
} else if (aggregation.getName().startsWith(Constants.FACET_QUERY_PREFIX)) {
60-
final Filter queryFacet = (Filter) aggregation;
61-
final String encodedQuery = queryFacet.getName().substring(Constants.FACET_QUERY_PREFIX.length());
62-
queryCountMap.put(new String(BaseEncoding.base64().decode(encodedQuery), StandardCharsets.UTF_8), queryFacet.getDocCount());
63-
}
64-
65-
});
55+
if (aggregations != null) {
56+
aggregations.forEach(aggregation -> {
57+
if (aggregation.getName().startsWith(Constants.FACET_FIELD_PREFIX)) {
58+
final Terms termFacet = (Terms) aggregation;
59+
fieldList.add(new Field(termFacet));
60+
} else if (aggregation.getName().startsWith(Constants.FACET_QUERY_PREFIX)) {
61+
final Filter queryFacet = (Filter) aggregation;
62+
final String encodedQuery = queryFacet.getName().substring(Constants.FACET_QUERY_PREFIX.length());
63+
queryCountMap.put(new String(BaseEncoding.base64().decode(encodedQuery), StandardCharsets.UTF_8), queryFacet.getDocCount());
64+
}
65+
66+
});
67+
}
6668
}
6769

6870
/**

src/main/resources/fess_rankfusion.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<components>
55
<component name="rankFusionProcessor"
66
class="org.codelibs.fess.rank.fusion.RankFusionProcessor">
7-
<postConstruct name="setSeacher">
7+
<postConstruct name="setSearcher">
88
<arg>
99
<component
1010
class="org.codelibs.fess.rank.fusion.DefaultSearcher">

0 commit comments

Comments
 (0)