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 @ SuppressWarnings ({"unchecked" , "rawtypes" })
149166 public void search (List <LeafReaderContext > leaves , Weight weight , CollectorManager manager ,
150167 QuerySearchResult result , DocValueFormat [] formats , TotalHits totalHits ) throws IOException {
@@ -180,7 +197,7 @@ protected void search(List<LeafReaderContext> leaves, Weight weight, Collector c
180197 * the provided <code>ctx</code>.
181198 */
182199 private void searchLeaf (LeafReaderContext ctx , Weight weight , Collector collector ) throws IOException {
183- checkCancelled ();
200+ cancellable . checkCancelled ();
184201 weight = wrapWeight (weight );
185202 final LeafCollector leafCollector ;
186203 try {
@@ -208,7 +225,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
208225 if (scorer != null ) {
209226 try {
210227 intersectScorerAndBitSet (scorer , liveDocsBitSet , leafCollector ,
211- checkCancelled == null ? () -> { } : checkCancelled );
228+ this . cancellable . isEnabled () ? cancellable :: checkCancelled : () -> {} );
212229 } catch (CollectionTerminatedException e ) {
213230 // collection was terminated prematurely
214231 // continue with the following leaf
@@ -218,7 +235,7 @@ private void searchLeaf(LeafReaderContext ctx, Weight weight, Collector collecto
218235 }
219236
220237 private Weight wrapWeight (Weight weight ) {
221- if (checkCancelled != null ) {
238+ if (cancellable . isEnabled () ) {
222239 return new Weight (weight .getQuery ()) {
223240 @ Override
224241 public void extractTerms (Set <Term > terms ) {
@@ -244,7 +261,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
244261 public BulkScorer bulkScorer (LeafReaderContext context ) throws IOException {
245262 BulkScorer in = weight .bulkScorer (context );
246263 if (in != null ) {
247- return new CancellableBulkScorer (in , checkCancelled );
264+ return new CancellableBulkScorer (in , cancellable :: checkCancelled );
248265 } else {
249266 return null ;
250267 }
@@ -320,4 +337,33 @@ public DirectoryReader getDirectoryReader() {
320337 assert reader instanceof DirectoryReader : "expected an instance of DirectoryReader, got " + reader .getClass ();
321338 return (DirectoryReader ) reader ;
322339 }
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+ }
323369}
0 commit comments