Skip to content

Commit a665cd4

Browse files
authored
Fix missing removal of query cancellation callback in QueryPhase (#130279)
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
1 parent 10fe741 commit a665cd4

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
@@ -908,6 +908,17 @@ private SearchPhaseResult executeQueryPhase(ShardSearchRequest request, Cancella
908908
}
909909
if (request.numberOfShards() == 1 && (request.source() == null || request.source().rankBuilder() == null)) {
910910
// we already have query results, but we can run fetch at the same time
911+
// in this case we reuse the search context across search and fetch phase, hence we need to clear the cancellation
912+
// checks that were applied by the query phase before running fetch. Note that the timeout checks are not applied
913+
// to the fetch phase, while the cancellation checks are.
914+
context.searcher().clearQueryCancellations();
915+
if (context.lowLevelCancellation()) {
916+
context.searcher().addQueryCancellation(() -> {
917+
if (task != null) {
918+
task.ensureNotCancelled();
919+
}
920+
});
921+
}
911922
context.addFetchResult();
912923
return executeFetchPhase(readerContext, context, afterQueryTime);
913924
} 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
@@ -187,6 +187,11 @@ public void close() {
187187
this.cancellable.clear();
188188
}
189189

190+
// clear all registered cancellation callbacks to prevent them from leaking into other phases
191+
public void clearQueryCancellations() {
192+
this.cancellable.clear();
193+
}
194+
190195
public boolean hasCancellations() {
191196
return this.cancellable.isEnabled();
192197
}

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

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

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

0 commit comments

Comments
 (0)