62
62
import java .io .IOException ;
63
63
import java .util .ArrayList ;
64
64
import java .util .Arrays ;
65
+ import java .util .HashSet ;
65
66
import java .util .List ;
67
+ import java .util .Objects ;
66
68
import java .util .Set ;
67
69
68
70
/**
@@ -77,25 +79,46 @@ public class ContextIndexSearcher extends IndexSearcher {
77
79
78
80
private AggregatedDfs aggregatedDfs ;
79
81
private QueryProfiler profiler ;
80
- private Runnable checkCancelled ;
82
+ private MutableQueryTimeout cancellable ;
81
83
82
- public ContextIndexSearcher (IndexReader reader , Similarity similarity , QueryCache queryCache , QueryCachingPolicy queryCachingPolicy ) {
83
- super (reader );
84
+ public ContextIndexSearcher (IndexReader reader , Similarity similarity ,
85
+ QueryCache queryCache , QueryCachingPolicy queryCachingPolicy ) throws IOException {
86
+ this (reader , similarity , queryCache , queryCachingPolicy , new MutableQueryTimeout ());
87
+ }
88
+
89
+ // TODO: Make the 2nd constructor private so that the IndexReader is always wrapped.
90
+ // Some issues must be fixed:
91
+ // - regarding tests deriving from AggregatorTestCase and more specifically the use of searchAndReduce and
92
+ // the ShardSearcher sub-searchers.
93
+ // - tests that use a MultiReader
94
+ public ContextIndexSearcher (IndexReader reader , Similarity similarity ,
95
+ QueryCache queryCache , QueryCachingPolicy queryCachingPolicy ,
96
+ MutableQueryTimeout cancellable ) throws IOException {
97
+ super (cancellable != null ? new ExitableDirectoryReader ((DirectoryReader ) reader , cancellable ) : reader );
84
98
setSimilarity (similarity );
85
99
setQueryCache (queryCache );
86
100
setQueryCachingPolicy (queryCachingPolicy );
101
+ this .cancellable = cancellable != null ? cancellable : new MutableQueryTimeout ();
87
102
}
88
103
89
104
public void setProfiler (QueryProfiler profiler ) {
90
105
this .profiler = profiler ;
91
106
}
92
107
93
108
/**
94
- * Set a {@link Runnable} that will be run on a regular basis while
95
- * collecting documents.
109
+ * Add a {@link Runnable} that will be run on a regular basis while accessing documents in the
110
+ * DirectoryReader but also while collecting them and check for query cancellation or timeout.
111
+ */
112
+ public Runnable addQueryCancellation (Runnable action ) {
113
+ return this .cancellable .add (action );
114
+ }
115
+
116
+ /**
117
+ * Remove a {@link Runnable} that checks for query cancellation or timeout
118
+ * which is called while accessing documents in the DirectoryReader but also while collecting them.
96
119
*/
97
- public void setCheckCancelled (Runnable checkCancelled ) {
98
- this .checkCancelled = checkCancelled ;
120
+ public void removeQueryCancellation (Runnable action ) {
121
+ this .cancellable . remove ( action ) ;
99
122
}
100
123
101
124
public void setAggregatedDfs (AggregatedDfs aggregatedDfs ) {
@@ -139,12 +162,6 @@ public Weight createWeight(Query query, ScoreMode scoreMode, float boost) throws
139
162
}
140
163
}
141
164
142
- private void checkCancelled () {
143
- if (checkCancelled != null ) {
144
- checkCancelled .run ();
145
- }
146
- }
147
-
148
165
@ SuppressWarnings ({"unchecked" , "rawtypes" })
149
166
public void search (List <LeafReaderContext > leaves , Weight weight , CollectorManager manager ,
150
167
QuerySearchResult result , DocValueFormat [] formats , TotalHits totalHits ) throws IOException {
@@ -180,7 +197,7 @@ protected void search(List<LeafReaderContext> leaves, Weight weight, Collector c
180
197
* the provided <code>ctx</code>.
181
198
*/
182
199
private void searchLeaf (LeafReaderContext ctx , Weight weight , Collector collector ) throws IOException {
183
- checkCancelled ();
200
+ cancellable . checkCancelled ();
184
201
weight = wrapWeight (weight );
185
202
final LeafCollector leafCollector ;
186
203
try {
@@ -208,7 +225,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
208
225
if (scorer != null ) {
209
226
try {
210
227
intersectScorerAndBitSet (scorer , liveDocsBitSet , leafCollector ,
211
- checkCancelled == null ? () -> { } : checkCancelled );
228
+ this . cancellable . isEnabled () ? cancellable :: checkCancelled : () -> {} );
212
229
} catch (CollectionTerminatedException e ) {
213
230
// collection was terminated prematurely
214
231
// continue with the following leaf
@@ -218,7 +235,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
218
235
}
219
236
220
237
private Weight wrapWeight (Weight weight ) {
221
- if (checkCancelled != null ) {
238
+ if (cancellable . isEnabled () ) {
222
239
return new Weight (weight .getQuery ()) {
223
240
@ Override
224
241
public void extractTerms (Set <Term > terms ) {
@@ -244,7 +261,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
244
261
public BulkScorer bulkScorer (LeafReaderContext context ) throws IOException {
245
262
BulkScorer in = weight .bulkScorer (context );
246
263
if (in != null ) {
247
- return new CancellableBulkScorer (in , checkCancelled );
264
+ return new CancellableBulkScorer (in , cancellable :: checkCancelled );
248
265
} else {
249
266
return null ;
250
267
}
@@ -320,4 +337,33 @@ public DirectoryReader getDirectoryReader() {
320
337
assert reader instanceof DirectoryReader : "expected an instance of DirectoryReader, got " + reader .getClass ();
321
338
return (DirectoryReader ) reader ;
322
339
}
340
+
341
+ private static class MutableQueryTimeout implements ExitableDirectoryReader .QueryCancellation {
342
+
343
+ private final Set <Runnable > runnables = new HashSet <>();
344
+
345
+ private Runnable add (Runnable action ) {
346
+ Objects .requireNonNull (action , "cancellation runnable should not be null" );
347
+ if (runnables .add (action ) == false ) {
348
+ throw new IllegalArgumentException ("Cancellation runnable already added" );
349
+ }
350
+ return action ;
351
+ }
352
+
353
+ private void remove (Runnable action ) {
354
+ runnables .remove (action );
355
+ }
356
+
357
+ @ Override
358
+ public void checkCancelled () {
359
+ for (Runnable timeout : runnables ) {
360
+ timeout .run ();
361
+ }
362
+ }
363
+
364
+ @ Override
365
+ public boolean isEnabled () {
366
+ return runnables .isEmpty () == false ;
367
+ }
368
+ }
323
369
}
0 commit comments