package org.threadly.concurrent.wrapper.limiter; import static org.junit.Assert.*; import static org.threadly.TestConstants.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.threadly.BlockingTestRunnable; import org.threadly.concurrent.DoNothingRunnable; import org.threadly.concurrent.PriorityScheduler; import org.threadly.concurrent.PrioritySchedulerTest.PrioritySchedulerFactory; import org.threadly.concurrent.StrictPriorityScheduler; import org.threadly.concurrent.SubmitterExecutor; import org.threadly.concurrent.SubmitterExecutorInterfaceTest; import org.threadly.test.concurrent.AsyncVerifier; import org.threadly.test.concurrent.TestRunnable; @SuppressWarnings("javadoc") public class ExecutorLimiterTest extends SubmitterExecutorInterfaceTest { protected static final int PARALLEL_COUNT = TEST_QTY / 2; protected static final int THREAD_COUNT = PARALLEL_COUNT * 2; protected static PriorityScheduler scheduler; @BeforeClass public static void setupClass() { scheduler = new StrictPriorityScheduler(THREAD_COUNT); SubmitterExecutorInterfaceTest.setupClass(); } @AfterClass public static void cleanupClass() { scheduler.shutdownNow(); scheduler = null; } protected ExecutorLimiter getLimiter(int parallelCount) { return new ExecutorLimiter(scheduler, parallelCount); } @Override protected SubmitterExecutorFactory getSubmitterExecutorFactory() { return new ExecutorLimiterFactory(); } @Test @SuppressWarnings("unused") public void constructorFail() { try { new ExecutorLimiter(null, 100); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { new ExecutorLimiter(Executors.newSingleThreadExecutor(), 0); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } } @Test public void getMaxConcurrencyTest() { assertEquals(PARALLEL_COUNT, getLimiter(PARALLEL_COUNT).getMaxConcurrency()); } @Test public void getUnsubmittedTaskCountTest() { ExecutorLimiter limiter = getLimiter(1); assertEquals(0, limiter.getUnsubmittedTaskCount()); BlockingTestRunnable btr = new BlockingTestRunnable(); try { limiter.execute(btr); // block till started, and first check should still be zero btr.blockTillStarted(); for (int i = 0; i < TEST_QTY; i ++) { assertEquals(i, limiter.getUnsubmittedTaskCount()); limiter.execute(DoNothingRunnable.instance()); } } finally { btr.unblock(); } } @Test public void consumeAvailableTest() { ExecutorLimiter limiter = getLimiter(PARALLEL_COUNT); List<TestRunnable> runnables = new ArrayList<>(PARALLEL_COUNT); for (int i = 0; i < PARALLEL_COUNT; i++) { TestRunnable tr = new TestRunnable(); runnables.add(tr); limiter.waitingTasks.add(limiter.new LimiterRunnableWrapper(limiter.executor, tr)); } limiter.consumeAvailable(); // should be fully consumed assertEquals(0, limiter.waitingTasks.size()); Iterator<TestRunnable> it = runnables.iterator(); while (it.hasNext()) { it.next().blockTillFinished(); // throws exception if it does not finish } } @Test public void executeLimitTest() throws InterruptedException, TimeoutException { Executor limitedExecutor = getLimiter(PARALLEL_COUNT); final AtomicInteger running = new AtomicInteger(0); final AsyncVerifier verifier = new AsyncVerifier(); List<TestRunnable> runnables = new ArrayList<>(TEST_QTY); for (int i = 0; i < TEST_QTY; i++) { TestRunnable tr = new TestRunnable(20) { @Override public void handleRunStart() { int runningCount = running.incrementAndGet(); if (runningCount > PARALLEL_COUNT) { verifier.fail(runningCount + " currently running"); } } @Override public void handleRunFinish() { running.decrementAndGet(); verifier.signalComplete(); } }; limitedExecutor.execute(tr); runnables.add(tr); } verifier.waitForTest(1000 * 10, TEST_QTY); // verify execution Iterator<TestRunnable> it = runnables.iterator(); while (it.hasNext()) { TestRunnable tr = it.next(); tr.blockTillFinished(); assertEquals(1, tr.getRunCount()); } } @Override @Test public void submitRunnableTest() throws InterruptedException, ExecutionException { ExecutorLimiterFactory elf = new ExecutorLimiterFactory(); try { ExecutorLimiter el = elf.makeSubmitterExecutor(TEST_QTY, false); List<TestRunnable> runnables = new ArrayList<>(TEST_QTY); List<Future<?>> futures = new ArrayList<>(TEST_QTY); for (int i = 0; i < TEST_QTY; i++) { TestRunnable tr = new TestRunnable(); Future<?> future = el.submit(tr); assertNotNull(future); runnables.add(tr); futures.add(future); } // verify execution Iterator<TestRunnable> it = runnables.iterator(); while (it.hasNext()) { TestRunnable tr = it.next(); tr.blockTillFinished(); assertEquals(1, tr.getRunCount()); } Iterator<Future<?>> futureIt = futures.iterator(); while (futureIt.hasNext()) { Future<?> f = futureIt.next(); try { f.get(); } catch (InterruptedException e) { fail(); } catch (ExecutionException e) { fail(); } assertTrue(f.isDone()); } super.submitRunnableTest(); } finally { elf.shutdown(); } } protected static class ExecutorLimiterFactory implements SubmitterExecutorFactory { private final PrioritySchedulerFactory schedulerFactory; protected ExecutorLimiterFactory() { schedulerFactory = new PrioritySchedulerFactory(); } @Override public ExecutorLimiter makeSubmitterExecutor(int poolSize, boolean prestartIfAvailable) { SubmitterExecutor executor = schedulerFactory.makeSubmitterExecutor(poolSize * 2, prestartIfAvailable); return new ExecutorLimiter(executor, poolSize); } @Override public void shutdown() { schedulerFactory.shutdown(); } } }