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
public void search (List <LeafReaderContext > leaves , Weight weight , CollectorManager manager ,
149
166
QuerySearchResult result , DocValueFormat [] formats , TotalHits totalHits ) throws IOException {
150
167
final List <Collector > collectors = new ArrayList <>(leaves .size ());
@@ -179,7 +196,7 @@ protected void search(List<LeafReaderContext> leaves, Weight weight, Collector c
179
196
* the provided <code>ctx</code>.
180
197
*/
181
198
private void searchLeaf (LeafReaderContext ctx , Weight weight , Collector collector ) throws IOException {
182
- checkCancelled ();
199
+ cancellable . checkCancelled ();
183
200
weight = wrapWeight (weight );
184
201
final LeafCollector leafCollector ;
185
202
try {
@@ -207,7 +224,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
207
224
if (scorer != null ) {
208
225
try {
209
226
intersectScorerAndBitSet (scorer , liveDocsBitSet , leafCollector ,
210
- checkCancelled == null ? () -> { } : checkCancelled );
227
+ this . cancellable . isEnabled () ? cancellable :: checkCancelled : () -> {} );
211
228
} catch (CollectionTerminatedException e ) {
212
229
// collection was terminated prematurely
213
230
// continue with the following leaf
@@ -217,7 +234,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
217
234
}
218
235
219
236
private Weight wrapWeight (Weight weight ) {
220
- if (checkCancelled != null ) {
237
+ if (cancellable . isEnabled () ) {
221
238
return new Weight (weight .getQuery ()) {
222
239
@ Override
223
240
public void extractTerms (Set <Term > terms ) {
@@ -243,7 +260,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
243
260
public BulkScorer bulkScorer (LeafReaderContext context ) throws IOException {
244
261
BulkScorer in = weight .bulkScorer (context );
245
262
if (in != null ) {
246
- return new CancellableBulkScorer (in , checkCancelled );
263
+ return new CancellableBulkScorer (in , cancellable :: checkCancelled );
247
264
} else {
248
265
return null ;
249
266
}
@@ -319,4 +336,33 @@ public DirectoryReader getDirectoryReader() {
319
336
assert reader instanceof DirectoryReader : "expected an instance of DirectoryReader, got " + reader .getClass ();
320
337
return (DirectoryReader ) reader ;
321
338
}
339
+
340
+ private static class MutableQueryTimeout implements ExitableDirectoryReader .QueryCancellation {
341
+
342
+ private final Set <Runnable > runnables = new HashSet <>();
343
+
344
+ private Runnable add (Runnable action ) {
345
+ Objects .requireNonNull (action , "cancellation runnable should not be null" );
346
+ if (runnables .add (action ) == false ) {
347
+ throw new IllegalArgumentException ("Cancellation runnable already added" );
348
+ }
349
+ return action ;
350
+ }
351
+
352
+ private void remove (Runnable action ) {
353
+ runnables .remove (action );
354
+ }
355
+
356
+ @ Override
357
+ public void checkCancelled () {
358
+ for (Runnable timeout : runnables ) {
359
+ timeout .run ();
360
+ }
361
+ }
362
+
363
+ @ Override
364
+ public boolean isEnabled () {
365
+ return runnables .isEmpty () == false ;
366
+ }
367
+ }
322
368
}
0 commit comments