2424import org .apache .lucene .index .NoMergePolicy ;
2525import org .apache .lucene .sandbox .document .HalfFloatPoint ;
2626import org .apache .lucene .search .Collector ;
27+ import org .apache .lucene .search .CollectorManager ;
2728import org .apache .lucene .search .IndexSearcher ;
2829import org .apache .lucene .search .MatchAllDocsQuery ;
2930import org .apache .lucene .search .Query ;
5859import org .elasticsearch .common .util .BigArrays ;
5960import org .elasticsearch .common .util .MockBigArrays ;
6061import org .elasticsearch .common .util .MockPageCacheRecycler ;
62+ import org .elasticsearch .common .util .concurrent .EsExecutors ;
6163import org .elasticsearch .core .CheckedConsumer ;
6264import org .elasticsearch .core .Releasable ;
6365import org .elasticsearch .core .Releasables ;
136138import org .elasticsearch .search .internal .SubSearchContext ;
137139import org .elasticsearch .test .ESTestCase ;
138140import org .elasticsearch .test .InternalAggregationTestCase ;
141+ import org .elasticsearch .threadpool .TestThreadPool ;
142+ import org .elasticsearch .threadpool .ThreadPool ;
139143import org .elasticsearch .xcontent .ContextParser ;
140144import org .elasticsearch .xcontent .XContentBuilder ;
141145import org .junit .After ;
145149import java .net .InetAddress ;
146150import java .util .ArrayList ;
147151import java .util .Arrays ;
152+ import java .util .Collection ;
148153import java .util .Collections ;
149154import java .util .HashMap ;
150155import java .util .HashSet ;
151156import java .util .List ;
152157import java .util .Map ;
153158import java .util .Optional ;
154159import java .util .Set ;
160+ import java .util .concurrent .ExecutionException ;
161+ import java .util .concurrent .ThreadPoolExecutor ;
155162import java .util .function .BiFunction ;
156163import java .util .function .Consumer ;
157164import java .util .stream .Stream ;
161168import static java .util .stream .Collectors .toList ;
162169import static org .elasticsearch .test .InternalAggregationTestCase .DEFAULT_MAX_BUCKETS ;
163170import static org .hamcrest .Matchers .equalTo ;
164- import static org .hamcrest .Matchers .in ;
165171import static org .hamcrest .Matchers .instanceOf ;
166172import static org .hamcrest .Matchers .not ;
167173import static org .hamcrest .Matchers .sameInstance ;
177183 */
178184public abstract class AggregatorTestCase extends ESTestCase {
179185 private NamedWriteableRegistry namedWriteableRegistry ;
180- private List <AggregationContext > releasables = new ArrayList <>();
186+ private final List <AggregationContext > releasables = new ArrayList <>();
181187 protected ValuesSourceRegistry valuesSourceRegistry ;
182188 private AnalysisModule analysisModule ;
183189
@@ -192,9 +198,21 @@ public abstract class AggregatorTestCase extends ESTestCase {
192198 CompletionFieldMapper .CONTENT_TYPE , // TODO support completion
193199 FieldAliasMapper .CONTENT_TYPE // TODO support alias
194200 );
201+ ThreadPool threadPool ;
202+ ThreadPoolExecutor threadPoolExecutor ;
195203
196204 @ Before
197205 public final void initPlugins () {
206+ int numThreads = randomIntBetween (2 , 4 );
207+ threadPool = new TestThreadPool (AggregatorTestCase .class .getName ());
208+ threadPoolExecutor = EsExecutors .newFixed (
209+ "test" ,
210+ numThreads ,
211+ 10 ,
212+ EsExecutors .daemonThreadFactory ("test" ),
213+ threadPool .getThreadContext (),
214+ randomBoolean ()
215+ );
198216 List <SearchPlugin > plugins = new ArrayList <>(getSearchPlugins ());
199217 plugins .add (new AggCardinalityUpperBoundPlugin ());
200218 SearchModule searchModule = new SearchModule (Settings .EMPTY , plugins );
@@ -475,7 +493,14 @@ private void runWithCrankyCircuitBreaker(IndexSettings indexSettings, IndexSearc
475493 } catch (CircuitBreakingException e ) {
476494 // Circuit breaks from the cranky breaker are expected - it randomly fails, after all
477495 assertThat (e .getMessage (), equalTo (CrankyCircuitBreakerService .ERROR_MESSAGE ));
478- } catch (IOException e ) {
496+ } catch (RuntimeException e ) {
497+ if (e .getCause () instanceof ExecutionException executionException ) {
498+ if (executionException .getCause () instanceof CircuitBreakingException circuitBreakingException ) {
499+ // Circuit breaks from the cranky breaker are expected - it randomly fails, after all
500+ assertThat (circuitBreakingException .getMessage (), equalTo (CrankyCircuitBreakerService .ERROR_MESSAGE ));
501+ return ;
502+ }
503+ }
479504 throw e ;
480505 }
481506 }
@@ -497,7 +522,7 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce(
497522
498523 final IndexReaderContext ctx = searcher .getTopReaderContext ();
499524 final PipelineTree pipelines = builder .buildPipelineTree ();
500- List <InternalAggregation > aggs = new ArrayList <>();
525+ List <InternalAggregation > internalAggs = new ArrayList <>();
501526 Query rewritten = searcher .rewrite (query );
502527
503528 if (splitLeavesIntoSeparateAggregators
@@ -533,7 +558,7 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce(
533558 }
534559 a .postCollection ();
535560 assertEquals (shouldBeCached , context .isCacheable ());
536- aggs .add (a .buildTopLevel ());
561+ internalAggs .add (a .buildTopLevel ());
537562 } finally {
538563 Releasables .close (context );
539564 }
@@ -550,39 +575,61 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce(
550575 fieldTypes
551576 );
552577 try {
553- C root = createAggregator (builder , context );
554- root .preCollection ();
578+ List <C > aggregators = new ArrayList <>();
555579 if (context .isInSortOrderExecutionRequired ()) {
580+ C root = createAggregator (builder , context );
581+ root .preCollection ();
582+ aggregators .add (root );
556583 new TimeSeriesIndexSearcher (searcher , List .of ()).search (rewritten , MultiBucketCollector .wrap (true , List .of (root )));
557584 } else {
558- searcher .search (rewritten , MultiBucketCollector .wrap (true , List .of (root )).asCollector ());
585+ CollectorManager <Collector , Void > collectorManager = new CollectorManager <>() {
586+ @ Override
587+ public Collector newCollector () throws IOException {
588+ C collector = createAggregator (builder , context );
589+ collector .preCollection ();
590+ aggregators .add (collector );
591+ return MultiBucketCollector .wrap (true , List .of (collector )).asCollector ();
592+ }
593+
594+ @ Override
595+ public Void reduce (Collection <Collector > collectors ) {
596+ return null ;
597+ }
598+ };
599+ if (aggTestConfig .builder ().supportsConcurrentExecution ()) {
600+ searcher .search (rewritten , collectorManager );
601+ } else {
602+ searcher .search (rewritten , collectorManager .newCollector ());
603+ }
604+ }
605+ for (C agg : aggregators ) {
606+ agg .postCollection ();
607+ internalAggs .add (agg .buildTopLevel ());
559608 }
560- root .postCollection ();
561- aggs .add (root .buildTopLevel ());
562609 } finally {
563610 Releasables .close (context );
564611 }
565612 }
566- assertRoundTrip (aggs );
613+ assertRoundTrip (internalAggs );
567614
568615 BigArrays bigArraysForReduction = new MockBigArrays (new MockPageCacheRecycler (Settings .EMPTY ), breakerService );
569616 try {
570- if (aggTestConfig .incrementalReduce () && aggs .size () > 1 ) {
617+ if (aggTestConfig .incrementalReduce () && internalAggs .size () > 1 ) {
571618 // sometimes do an incremental reduce
572- int toReduceSize = aggs .size ();
573- Collections .shuffle (aggs , random ());
619+ int toReduceSize = internalAggs .size ();
620+ Collections .shuffle (internalAggs , random ());
574621 int r = randomIntBetween (1 , toReduceSize );
575- List <InternalAggregation > toReduce = aggs .subList (0 , r );
622+ List <InternalAggregation > toReduce = internalAggs .subList (0 , r );
576623 AggregationReduceContext reduceContext = new AggregationReduceContext .ForPartial (
577624 bigArraysForReduction ,
578625 getMockScriptService (),
579626 () -> false ,
580627 builder
581628 );
582- A reduced = (A ) aggs .get (0 ).reduce (toReduce , reduceContext );
583- aggs = new ArrayList <>(aggs .subList (r , toReduceSize ));
584- aggs .add (reduced );
585- assertRoundTrip (aggs );
629+ A reduced = (A ) internalAggs .get (0 ).reduce (toReduce , reduceContext );
630+ internalAggs = new ArrayList <>(internalAggs .subList (r , toReduceSize ));
631+ internalAggs .add (reduced );
632+ assertRoundTrip (internalAggs );
586633 }
587634
588635 // now do the final reduce
@@ -600,7 +647,7 @@ private <A extends InternalAggregation, C extends Aggregator> A searchAndReduce(
600647 );
601648
602649 @ SuppressWarnings ("unchecked" )
603- A internalAgg = (A ) aggs .get (0 ).reduce (aggs , reduceContext );
650+ A internalAgg = (A ) internalAggs .get (0 ).reduce (internalAggs , reduceContext );
604651 assertRoundTrip (internalAgg );
605652
606653 // materialize any parent pipelines
@@ -870,16 +917,28 @@ protected static DirectoryReader wrapInMockESDirectoryReader(DirectoryReader dir
870917 }
871918
872919 /**
873- * Added to randomly run with more assertions on the index searcher level,
874- * like {@link org.apache.lucene.tests.util.LuceneTestCase#newSearcher(IndexReader)}, which can't be used because it also
875- * wraps in the IndexSearcher's IndexReader with other implementations that we can't handle. (e.g. ParallelCompositeReader)
920+ * Creates a {@link ContextIndexSearcher} that supports concurrency running each segment in a different thread. It randomly
921+ * sets the IndexSearcher to run on concurrent mode.
876922 */
877- protected static IndexSearcher newIndexSearcher (DirectoryReader indexReader ) throws IOException {
923+ protected IndexSearcher newIndexSearcher (DirectoryReader indexReader ) throws IOException {
878924 if (randomBoolean ()) {
879925 // this executes basic query checks and asserts that weights are normalized only once etc.
880926 return new AssertingIndexSearcher (random (), indexReader );
881927 } else {
882- return new IndexSearcher (indexReader );
928+ return new ContextIndexSearcher (
929+ indexReader ,
930+ IndexSearcher .getDefaultSimilarity (),
931+ IndexSearcher .getDefaultQueryCache (),
932+ IndexSearcher .getDefaultQueryCachingPolicy (),
933+ randomBoolean (),
934+ this .threadPoolExecutor
935+ ) {
936+ @ Override
937+ protected LeafSlice [] slices (List <LeafReaderContext > leaves ) {
938+ // get a thread per segment
939+ return slices (leaves , 1 , 1 );
940+ }
941+ };
883942 }
884943 }
885944
@@ -1179,6 +1238,8 @@ public IndexAnalyzers getIndexAnalyzers() {
11791238 public void cleanupReleasables () {
11801239 Releasables .close (releasables );
11811240 releasables .clear ();
1241+ threadPoolExecutor .shutdown ();
1242+ terminate (threadPool );
11821243 }
11831244
11841245 /**
0 commit comments