6262import java .io .IOException ;
6363import java .util .ArrayList ;
6464import java .util .Arrays ;
65+ import java .util .HashSet ;
6566import java .util .List ;
67+ import java .util .Objects ;
6668import java .util .Set ;
6769
6870/**
@@ -77,25 +79,46 @@ public class ContextIndexSearcher extends IndexSearcher {
7779
7880 private AggregatedDfs aggregatedDfs ;
7981 private QueryProfiler profiler ;
80- private Runnable checkCancelled ;
82+ private MutableQueryTimeout cancellable ;
8183
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 );
8498 setSimilarity (similarity );
8599 setQueryCache (queryCache );
86100 setQueryCachingPolicy (queryCachingPolicy );
101+ this .cancellable = cancellable != null ? cancellable : new MutableQueryTimeout ();
87102 }
88103
89104 public void setProfiler (QueryProfiler profiler ) {
90105 this .profiler = profiler ;
91106 }
92107
93108 /**
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.
96119 */
97- public void setCheckCancelled (Runnable checkCancelled ) {
98- this .checkCancelled = checkCancelled ;
120+ public void removeQueryCancellation (Runnable action ) {
121+ this .cancellable . remove ( action ) ;
99122 }
100123
101124 public void setAggregatedDfs (AggregatedDfs aggregatedDfs ) {
@@ -139,12 +162,6 @@ public Weight createWeight(Query query, ScoreMode scoreMode, float boost) throws
139162 }
140163 }
141164
142- private void checkCancelled () {
143- if (checkCancelled != null ) {
144- checkCancelled .run ();
145- }
146- }
147-
148165 public void search (List <LeafReaderContext > leaves , Weight weight , CollectorManager manager ,
149166 QuerySearchResult result , DocValueFormat [] formats , TotalHits totalHits ) throws IOException {
150167 final List <Collector > collectors = new ArrayList <>(leaves .size ());
@@ -179,7 +196,7 @@ protected void search(List<LeafReaderContext> leaves, Weight weight, Collector c
179196 * the provided <code>ctx</code>.
180197 */
181198 private void searchLeaf (LeafReaderContext ctx , Weight weight , Collector collector ) throws IOException {
182- checkCancelled ();
199+ cancellable . checkCancelled ();
183200 weight = wrapWeight (weight );
184201 final LeafCollector leafCollector ;
185202 try {
@@ -207,7 +224,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
207224 if (scorer != null ) {
208225 try {
209226 intersectScorerAndBitSet (scorer , liveDocsBitSet , leafCollector ,
210- checkCancelled == null ? () -> { } : checkCancelled );
227+ this . cancellable . isEnabled () ? cancellable :: checkCancelled : () -> {} );
211228 } catch (CollectionTerminatedException e ) {
212229 // collection was terminated prematurely
213230 // continue with the following leaf
@@ -217,7 +234,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
217234 }
218235
219236 private Weight wrapWeight (Weight weight ) {
220- if (checkCancelled != null ) {
237+ if (cancellable . isEnabled () ) {
221238 return new Weight (weight .getQuery ()) {
222239 @ Override
223240 public void extractTerms (Set <Term > terms ) {
@@ -243,7 +260,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
243260 public BulkScorer bulkScorer (LeafReaderContext context ) throws IOException {
244261 BulkScorer in = weight .bulkScorer (context );
245262 if (in != null ) {
246- return new CancellableBulkScorer (in , checkCancelled );
263+ return new CancellableBulkScorer (in , cancellable :: checkCancelled );
247264 } else {
248265 return null ;
249266 }
@@ -319,4 +336,33 @@ public DirectoryReader getDirectoryReader() {
319336 assert reader instanceof DirectoryReader : "expected an instance of DirectoryReader, got " + reader .getClass ();
320337 return (DirectoryReader ) reader ;
321338 }
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+ }
322368}
0 commit comments