package io.dropwizard.lifecycle.setup; import io.dropwizard.util.Duration; import org.junit.Before; import org.junit.Test; import org.mockito.exceptions.verification.WantedButNotInvoked; import org.slf4j.Logger; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; public class ExecutorServiceBuilderTest { private static final String WARNING = "Parameter 'maximumPoolSize' is conflicting with unbounded work queues"; private ExecutorServiceBuilder executorServiceBuilder; private Logger log; @Before public void setUp() throws Exception { executorServiceBuilder = new ExecutorServiceBuilder(new LifecycleEnvironment(), "test"); log = mock(Logger.class); ExecutorServiceBuilder.setLog(log); } @Test public void testGiveAWarningAboutMaximumPoolSizeAndUnboundedQueue() { executorServiceBuilder .minThreads(4) .maxThreads(8) .build(); verify(log).warn(WARNING); } @Test public void testGiveNoWarningAboutMaximumPoolSizeAndBoundedQueue() throws InterruptedException { ExecutorService exe = executorServiceBuilder .minThreads(4) .maxThreads(8) .workQueue(new ArrayBlockingQueue<>(16)) .build(); verify(log, never()).warn(WARNING); assertCanExecuteAtLeast2ConcurrentTasks(exe); } /** * There should be no warning about using a Executors.newSingleThreadExecutor() equivalent * @see java.util.concurrent.Executors#newSingleThreadExecutor() */ @Test public void shouldNotWarnWhenSettingUpSingleThreadedPool() { executorServiceBuilder .minThreads(1) .maxThreads(1) .keepAliveTime(Duration.milliseconds(0)) .workQueue(new LinkedBlockingQueue<>()) .build(); verify(log, never()).warn(anyString()); } /** * There should be no warning about using a Executors.newCachedThreadPool() equivalent * @see java.util.concurrent.Executors#newCachedThreadPool() */ @Test public void shouldNotWarnWhenSettingUpCachedThreadPool() throws InterruptedException { ExecutorService exe = executorServiceBuilder .minThreads(0) .maxThreads(Integer.MAX_VALUE) .keepAliveTime(Duration.seconds(60)) .workQueue(new SynchronousQueue<>()) .build(); verify(log, never()).warn(anyString()); assertCanExecuteAtLeast2ConcurrentTasks(exe); // cached thread pools work right? } @Test public void shouldNotWarnWhenUsingTheDefaultConfiguration() { executorServiceBuilder.build(); verify(log, never()).warn(anyString()); } /** * Setting large max threads without large min threads is misleading on the default queue implementation * It should warn or work */ @Test public void shouldBeAbleToExecute2TasksAtOnceWithLargeMaxThreadsOrBeWarnedOtherwise() { ExecutorService exe = executorServiceBuilder .maxThreads(Integer.MAX_VALUE) .build(); try { verify(log).warn(anyString()); } catch (WantedButNotInvoked error) { // no warning has been given so we should be able to execute at least 2 things at once assertCanExecuteAtLeast2ConcurrentTasks(exe); } } /** * Tries to run 2 tasks that on the executor that rely on each others side-effect to complete. If they fail to * complete within a short time then we can assume they are not running concurrently * @param exe an executor to try to run 2 tasks on */ private void assertCanExecuteAtLeast2ConcurrentTasks(Executor exe) { CountDownLatch latch = new CountDownLatch(2); Runnable concurrentLatchCountDownAndWait = () -> { latch.countDown(); try { latch.await(); } catch (InterruptedException ex) { throw new RuntimeException(ex); } }; exe.execute(concurrentLatchCountDownAndWait); exe.execute(concurrentLatchCountDownAndWait); try { // 1 second is ages even on a slow VM assertThat(latch.await(1, TimeUnit.SECONDS)) .as("2 tasks executed concurrently on " + exe) .isTrue(); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } }