1010package org .elasticsearch .index .engine ;
1111
1212import org .elasticsearch .common .settings .Settings ;
13+ import org .elasticsearch .common .util .concurrent .ConcurrentCollections ;
1314import org .elasticsearch .common .util .concurrent .DeterministicTaskQueue ;
15+ import org .elasticsearch .common .util .concurrent .EsExecutors ;
1416import org .elasticsearch .index .IndexSettings ;
1517import org .elasticsearch .test .ESTestCase ;
1618import org .elasticsearch .test .IndexSettingsModule ;
1719import org .elasticsearch .threadpool .TestThreadPool ;
1820import org .elasticsearch .threadpool .ThreadPool ;
1921import org .junit .Before ;
2022
23+ import java .util .Collection ;
24+ import java .util .concurrent .CountDownLatch ;
25+
26+ import static org .hamcrest .Matchers .equalTo ;
27+ import static org .mockito .ArgumentMatchers .anyDouble ;
28+ import static org .mockito .Mockito .doAnswer ;
2129import static org .mockito .Mockito .mock ;
2230import static org .mockito .Mockito .times ;
2331import static org .mockito .Mockito .verify ;
@@ -37,7 +45,7 @@ public void setUpThreadPool() {
3745 }
3846
3947 public void testMergeTasksAreAbortedWhenThreadPoolIsShutdown () {
40- final TestThreadPool testThreadPool = new TestThreadPool ("test" );
48+ TestThreadPool testThreadPool = new TestThreadPool ("test" );
4149 ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService
4250 .maybeCreateThreadPoolMergeExecutorService (
4351 testThreadPool ,
@@ -57,4 +65,66 @@ public void testMergeTasksAreAbortedWhenThreadPoolIsShutdown() {
5765 verify (mergeTask , times (0 )).run ();
5866 assertTrue (threadPoolMergeExecutorService .allDone ());
5967 }
68+
69+ public void testBackloggedMergeTasksAreAllExecutedExactlyOnce () throws Exception {
70+ int mergeExecutorThreadCount = randomIntBetween (1 , 2 );
71+ Settings settings = Settings .builder ()
72+ .put (ThreadPoolMergeScheduler .USE_THREAD_POOL_MERGE_SCHEDULER_SETTING .getKey (), true )
73+ // results in few merge threads, in order to increase contention
74+ .put (EsExecutors .NODE_PROCESSORS_SETTING .getKey (), mergeExecutorThreadCount )
75+ .build ();
76+ try (TestThreadPool testThreadPool = new TestThreadPool ("test" , settings )) {
77+ ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService
78+ .maybeCreateThreadPoolMergeExecutorService (testThreadPool , settings );
79+ assertNotNull (threadPoolMergeExecutorService );
80+ assertThat (threadPoolMergeExecutorService .getMaxConcurrentMerges (), equalTo (mergeExecutorThreadCount ));
81+ int mergeTaskCount = randomIntBetween (3 , 30 );
82+ CountDownLatch mergeTasksDoneLatch = new CountDownLatch (mergeTaskCount );
83+ CountDownLatch mergeTasksReadyLatch = new CountDownLatch (mergeTaskCount );
84+ CountDownLatch submitTaskLatch = new CountDownLatch (1 );
85+ Collection <ThreadPoolMergeScheduler .MergeTask > generatedMergeTasks = ConcurrentCollections .newConcurrentSet ();
86+ for (int i = 0 ; i < mergeTaskCount ; i ++) {
87+ new Thread (()-> {
88+ ThreadPoolMergeScheduler .MergeTask mergeTask = mock (ThreadPoolMergeScheduler .MergeTask .class );
89+ when (mergeTask .isRunning ()).thenReturn (false );
90+ boolean supportsIOThrottling = randomBoolean ();
91+ when (mergeTask .supportsIOThrottling ()).thenReturn (supportsIOThrottling );
92+ long mergeSize = randomNonNegativeLong ();
93+ when (mergeTask .estimatedMergeSize ()).thenReturn (mergeSize );
94+ doAnswer (mock -> {
95+ // each individual merge task can either "run" or be "backlogged"
96+ boolean runNowOrBacklog = randomBoolean ();
97+ if (runNowOrBacklog ) {
98+ mergeTasksDoneLatch .countDown ();
99+ } else {
100+ testThreadPool .executor (ThreadPool .Names .GENERIC ).execute (() -> {
101+ // reenqueue backlogged merge task
102+ threadPoolMergeExecutorService .reEnqueueBackloggedMergeTask (mergeTask );
103+ });
104+ }
105+ return runNowOrBacklog ;
106+ }).when (mergeTask ).runNowOrBacklog ();
107+ generatedMergeTasks .add (mergeTask );
108+ mergeTasksReadyLatch .countDown ();
109+ // make all threads submit merge tasks at once
110+ safeAwait (submitTaskLatch );
111+ threadPoolMergeExecutorService .submitMergeTask (mergeTask );
112+ }).start ();
113+ }
114+ safeAwait (mergeTasksReadyLatch );
115+ submitTaskLatch .countDown ();
116+ safeAwait (mergeTasksDoneLatch );
117+ assertBusy (() -> {
118+ for (ThreadPoolMergeScheduler .MergeTask mergeTask : generatedMergeTasks ) {
119+ verify (mergeTask , times (1 )).run ();
120+ if (mergeTask .supportsIOThrottling ()) {
121+ verify (mergeTask ).setIORateLimit (anyDouble ());
122+ } else {
123+ verify (mergeTask , times (0 )).setIORateLimit (anyDouble ());
124+ }
125+ }
126+ threadPoolMergeExecutorService .allDone ();
127+ });
128+ }
129+ }
60130}
0 commit comments