Skip to content

Commit ebc1016

Browse files
piergmjessepeixotojavanna
authored
Fix missing removal of query cancellation callback in QueryPhase (#130279) (#131812)
When a search targets a single shard, the fetch phase is executed in the same roundtrip as the query phase. In that case the search context is reused across phases, and the cancellation runnables registered to the searcher are reused. This is incorrect as the search timeout check should not be applied to the fetch phase, or it will make fetching partial results unfeasible. For instance if the query phase times out, the fetch phase will time out as well and there won't be partial results. If the query phase does not time out, but the fetch phase does, the latter will return a different set of results compared to those expected which will lead to unexpected situations like an array index out of bound exception. This commit clears the cancellation runnables ahead of running the fetch phase in the same rountrip as query and performs their registration once again, without the timeout checks which are never applied to the fetch phase. Closes #130071 Co-authored-by: jessepeixoto <[email protected]> Co-authored-by: Luca Cavanna <[email protected]>
1 parent 7534840 commit ebc1016

File tree

4 files changed

+44
-0
lines changed

4 files changed

+44
-0
lines changed

docs/changelog/130279.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 130279
2+
summary: Fix missing removal of query cancellation callback in QueryPhase
3+
area: Search
4+
type: bug
5+
issues: [130071]

server/src/main/java/org/elasticsearch/search/SearchService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,17 @@ private SearchPhaseResult executeQueryPhase(ShardSearchRequest request, SearchSh
796796
}
797797
if (request.numberOfShards() == 1 && (request.source() == null || request.source().rankBuilder() == null)) {
798798
// we already have query results, but we can run fetch at the same time
799+
// in this case we reuse the search context across search and fetch phase, hence we need to clear the cancellation
800+
// checks that were applied by the query phase before running fetch. Note that the timeout checks are not applied
801+
// to the fetch phase, while the cancellation checks are.
802+
context.searcher().clearQueryCancellations();
803+
if (context.lowLevelCancellation()) {
804+
context.searcher().addQueryCancellation(() -> {
805+
if (task != null) {
806+
task.ensureNotCancelled();
807+
}
808+
});
809+
}
799810
context.addFetchResult();
800811
return executeFetchPhase(readerContext, context, afterQueryTime);
801812
} else {

server/src/main/java/org/elasticsearch/search/internal/ContextIndexSearcher.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ public void close() {
183183
this.cancellable.clear();
184184
}
185185

186+
// clear all registered cancellation callbacks to prevent them from leaking into other phases
187+
public void clearQueryCancellations() {
188+
this.cancellable.clear();
189+
}
190+
186191
public boolean hasCancellations() {
187192
return this.cancellable.isEnabled();
188193
}

server/src/test/java/org/elasticsearch/search/internal/ContextIndexSearcherTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ private static void assertSlices(LeafSlice[] slices, int numDocs, int numThreads
401401
assertThat(sumDocs, equalTo(numDocs));
402402
}
403403

404+
public void testClearQueryCancellations() throws IOException {
405+
Directory dir = newDirectory();
406+
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
407+
w.addDocument(new Document());
408+
DirectoryReader reader = w.getReader();
409+
ContextIndexSearcher searcher = new ContextIndexSearcher(
410+
reader,
411+
IndexSearcher.getDefaultSimilarity(),
412+
IndexSearcher.getDefaultQueryCache(),
413+
IndexSearcher.getDefaultQueryCachingPolicy(),
414+
true
415+
);
416+
417+
assertFalse(searcher.hasCancellations());
418+
searcher.addQueryCancellation(() -> {});
419+
assertTrue(searcher.hasCancellations());
420+
421+
searcher.clearQueryCancellations();
422+
assertFalse(searcher.hasCancellations());
423+
424+
IOUtils.close(reader, w, dir);
425+
}
426+
404427
public void testExitableTermsMinAndMax() throws IOException {
405428
Directory dir = newDirectory();
406429
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(null));

0 commit comments

Comments
 (0)