21
21
import org .apache .lucene .search .TopScoreDocCollectorManager ;
22
22
import org .apache .lucene .util .RamUsageEstimator ;
23
23
import org .elasticsearch .common .Strings ;
24
- import org .elasticsearch .common .breaker .CircuitBreaker ;
25
- import org .elasticsearch .compute .data .BlockFactory ;
26
24
import org .elasticsearch .compute .data .DocBlock ;
27
25
import org .elasticsearch .compute .data .DocVector ;
28
26
import org .elasticsearch .compute .data .DoubleBlock ;
42
40
import java .util .Arrays ;
43
41
import java .util .List ;
44
42
import java .util .Optional ;
43
+ import java .util .concurrent .TimeUnit ;
45
44
import java .util .function .Function ;
46
45
import java .util .stream .Collectors ;
47
46
@@ -91,8 +90,7 @@ public Factory(
91
90
public SourceOperator get (DriverContext driverContext ) {
92
91
return new LuceneTopNSourceOperator (
93
92
contexts ,
94
- driverContext .breaker (),
95
- driverContext .blockFactory (),
93
+ driverContext ,
96
94
maxPageSize ,
97
95
sorts ,
98
96
estimatedPerRowSortSize ,
@@ -126,7 +124,10 @@ public String describe() {
126
124
// We use the same value as the INITIAL_INTERVAL from CancellableBulkScorer
127
125
private static final int NUM_DOCS_INTERVAL = 1 << 12 ;
128
126
129
- private final CircuitBreaker breaker ;
127
+ // The max time we should be spending scoring docs in one getOutput call, before we return to update the driver status
128
+ private static final long MAX_EXEC_TIME = TimeUnit .SECONDS .toNanos (1 );
129
+
130
+ private final DriverContext driverContext ;
130
131
private final List <SortBuilder <?>> sorts ;
131
132
private final long estimatedPerRowSortSize ;
132
133
private final int limit ;
@@ -151,22 +152,21 @@ public String describe() {
151
152
152
153
public LuceneTopNSourceOperator (
153
154
List <? extends ShardContext > contexts ,
154
- CircuitBreaker breaker ,
155
- BlockFactory blockFactory ,
155
+ DriverContext driverContext ,
156
156
int maxPageSize ,
157
157
List <SortBuilder <?>> sorts ,
158
158
long estimatedPerRowSortSize ,
159
159
int limit ,
160
160
LuceneSliceQueue sliceQueue ,
161
161
boolean needsScore
162
162
) {
163
- super (contexts , blockFactory , maxPageSize , sliceQueue );
164
- this .breaker = breaker ;
163
+ super (contexts , driverContext . blockFactory () , maxPageSize , sliceQueue );
164
+ this .driverContext = driverContext ;
165
165
this .sorts = sorts ;
166
166
this .estimatedPerRowSortSize = estimatedPerRowSortSize ;
167
167
this .limit = limit ;
168
168
this .needsScore = needsScore ;
169
- breaker .addEstimateBytesAndMaybeBreak (reserveSize (), "esql lucene topn" );
169
+ driverContext . breaker () .addEstimateBytesAndMaybeBreak (reserveSize (), "esql lucene topn" );
170
170
}
171
171
172
172
@ Override
@@ -201,34 +201,49 @@ public Page getCheckedOutput() throws IOException {
201
201
202
202
private Page collect () throws IOException {
203
203
assert doneCollecting == false ;
204
+ long start = System .nanoTime ();
205
+
204
206
var scorer = getCurrentOrLoadNextScorer ();
205
- if (scorer == null ) {
206
- doneCollecting = true ;
207
- startEmitting ();
208
- return emit ();
209
- }
210
- try {
207
+
208
+ while (scorer != null ) {
211
209
if (scorer .tags ().isEmpty () == false ) {
212
210
throw new UnsupportedOperationException ("tags not supported by " + getClass ());
213
211
}
214
- if (perShardCollector == null || perShardCollector .shardContext .index () != scorer .shardContext ().index ()) {
215
- // TODO: share the bottom between shardCollectors
216
- perShardCollector = newPerShardCollector (scorer .shardContext (), sorts , needsScore , limit );
212
+
213
+ try {
214
+ if (perShardCollector == null || perShardCollector .shardContext .index () != scorer .shardContext ().index ()) {
215
+ // TODO: share the bottom between shardCollectors
216
+ perShardCollector = newPerShardCollector (scorer .shardContext (), sorts , needsScore , limit );
217
+ }
218
+ var leafCollector = perShardCollector .getLeafCollector (scorer .leafReaderContext ());
219
+ scorer .scoreNextRange (leafCollector , scorer .leafReaderContext ().reader ().getLiveDocs (), NUM_DOCS_INTERVAL );
220
+ } catch (CollectionTerminatedException cte ) {
221
+ // Lucene terminated early the collection (doing topN for an index that's sorted and the topN uses the same sorting)
222
+ scorer .markAsDone ();
223
+ }
224
+
225
+ // check if the query has been cancelled.
226
+ driverContext .checkForEarlyTermination ();
227
+
228
+ if (scorer .isDone ()) {
229
+ var nextScorer = getCurrentOrLoadNextScorer ();
230
+ if (nextScorer != null && nextScorer .shardContext ().index () != scorer .shardContext ().index ()) {
231
+ startEmitting ();
232
+ return emit ();
233
+ }
234
+ scorer = nextScorer ;
217
235
}
218
- var leafCollector = perShardCollector .getLeafCollector (scorer .leafReaderContext ());
219
- scorer .scoreNextRange (leafCollector , scorer .leafReaderContext ().reader ().getLiveDocs (), NUM_DOCS_INTERVAL );
220
- } catch (CollectionTerminatedException cte ) {
221
- // Lucene terminated early the collection (doing topN for an index that's sorted and the topN uses the same sorting)
222
- scorer .markAsDone ();
223
- }
224
- if (scorer .isDone ()) {
225
- var nextScorer = getCurrentOrLoadNextScorer ();
226
- if (nextScorer == null || nextScorer .shardContext ().index () != scorer .shardContext ().index ()) {
227
- startEmitting ();
228
- return emit ();
236
+
237
+ // When it takes a long time to start emitting pages we need to return back to the driver so we can update its status.
238
+ // Even if this should almost never happen, we want to update the driver status even when a query runs "forever".
239
+ if (System .nanoTime () - start > MAX_EXEC_TIME ) {
240
+ return null ;
229
241
}
230
242
}
231
- return null ;
243
+
244
+ doneCollecting = true ;
245
+ startEmitting ();
246
+ return emit ();
232
247
}
233
248
234
249
private boolean isEmitting () {
@@ -348,7 +363,7 @@ protected void describe(StringBuilder sb) {
348
363
349
364
@ Override
350
365
protected void additionalClose () {
351
- Releasables .close (() -> breaker .addWithoutBreaking (-reserveSize ()));
366
+ Releasables .close (() -> driverContext . breaker () .addWithoutBreaking (-reserveSize ()));
352
367
}
353
368
354
369
private long reserveSize () {
0 commit comments