/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package org.apereo.portal.utils.threading; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RunnableFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.Ignore; import org.junit.Test; /** * Test for {@link ThreadPoolExecutor} that verifies threads are only created on demand * */ @Ignore public class DynamicThreadPoolExecutorTest { @Test public void testExecutorsNewCachedThreadPool() throws Exception { //See Executors.newCachedThreadPool(); final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 0, 2, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) { @Override protected void afterExecute(Runnable r, Throwable t) { //This happens after Future.get() returns so it reduces the chance for timing issues in the test final LatchFutureTask lr = (LatchFutureTask) r; lr.done(); } @SuppressWarnings("unchecked") @Override protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { if (runnable instanceof RunnableFuture) { return (RunnableFuture<T>) runnable; } return super.newTaskFor(runnable, value); } }; testThreadPoolExecutor(threadPoolExecutor, false); } protected void testThreadPoolExecutor( final ThreadPoolExecutor threadPoolExecutor, boolean queuesAdditional) throws InterruptedException, ExecutionException { final ExecutorStats executorStats = new ExecutorStats(); //Nothing going on in a new pool executorStats.verify(threadPoolExecutor); //****************************************// //Schedule task 1 final LatchFutureTask lr1 = LatchFutureTask.create(); threadPoolExecutor.submit(lr1); //Verify 1 is running lr1.waitForStart(); executorStats.activeCount++; executorStats.largestPoolSize++; executorStats.poolSize++; executorStats.verify(threadPoolExecutor); //Verify 1 is stopped lr1.waitForDone(); //Sadly need to sleep just long enough to let the completed thread get back into the pool Thread.sleep(5); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //****************************************// //Schedule task 2 final LatchFutureTask lr2 = LatchFutureTask.create(); threadPoolExecutor.submit(lr2); //Verify 2 is running lr2.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Verify 2 is stopped lr2.waitForDone(); //Sadly need to sleep just long enough to let the completed thread get back into the pool Thread.sleep(5); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //****************************************// //Schedule task 3 final LatchFutureTask lr3 = LatchFutureTask.create(); threadPoolExecutor.submit(lr3); //Verify 3 is running lr3.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Schedule task 4 concurrently final LatchFutureTask lr4 = LatchFutureTask.create(); threadPoolExecutor.submit(lr4); //Verify 4 is running lr4.waitForStart(); executorStats.activeCount++; executorStats.poolSize++; executorStats.largestPoolSize++; executorStats.verify(threadPoolExecutor); //Verify 3 is stopped lr3.waitForDone(); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //Verify 4 is stopped lr4.waitForDone(); //Sadly need to sleep just long enough to let the completed thread get back into the pool Thread.sleep(5); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //****************************************// //Schedule task 5 final LatchFutureTask lr5 = LatchFutureTask.create(); threadPoolExecutor.submit(lr5); //Verify 5 is running lr5.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Verify 5 is stopped lr5.waitForDone(); //Sadly need to sleep just long enough to let the completed thread get back into the pool Thread.sleep(5); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //****************************************// //Schedule task 6 final LatchFutureTask lr6 = LatchFutureTask.create(); threadPoolExecutor.submit(lr6); //Verify 6 is running lr6.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Schedule task 7 concurrently final LatchFutureTask lr7 = LatchFutureTask.create(); threadPoolExecutor.submit(lr7); //Verify 7 is running lr7.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Schedule task 8 concurrently final LatchFutureTask lr8 = LatchFutureTask.create(); if (queuesAdditional) { threadPoolExecutor.submit(lr8); executorStats.verify(threadPoolExecutor); //Stop 6 to make room in pool for 8 //Verify 6 is stopped lr6.waitForDone(); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //Verify 8 is running lr8.waitForStart(); executorStats.activeCount++; executorStats.verify(threadPoolExecutor); //Verify 8 is stopped lr8.waitForDone(); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); } else { try { threadPoolExecutor.submit(lr8); fail("submit should have thrown RejectedExecutionException"); } catch (RejectedExecutionException e) { //Expected } //Verify 6 is stopped lr6.waitForDone(); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); } //Verify 7 is stopped lr7.waitForDone(); //Sadly need to sleep just long enough to let the completed thread get back into the pool Thread.sleep(5); executorStats.activeCount--; executorStats.completedTaskCount++; executorStats.verify(threadPoolExecutor); //****************************************// } private static final class ExecutorStats { public int activeCount = 0; public int completedTaskCount = 0; public int corePoolSize = 0; public int largestPoolSize = 0; public int poolSize = 0; public void verify(ThreadPoolExecutor executor) { assertEquals( "Active Thread Counts don't match", activeCount, executor.getActiveCount()); assertEquals( "Completed Task Counts don't match", completedTaskCount, executor.getCompletedTaskCount()); assertEquals("Core Pool Sizes don't match", corePoolSize, executor.getCorePoolSize()); assertEquals("Pool Sizes don't match", poolSize, executor.getPoolSize()); assertEquals( "Largest Pool Sizes don't match", largestPoolSize, executor.getLargestPoolSize()); } } private static final class LatchFutureTask extends FutureTask<Object> { private final LatchRunnable latchRunnable; public static LatchFutureTask create() { return new LatchFutureTask(new LatchRunnable()); } private LatchFutureTask(LatchRunnable latchRunnable) { super(latchRunnable, null); this.latchRunnable = latchRunnable; } public void waitForStart() throws InterruptedException { latchRunnable.waitForStart(); } public void waitForDone() throws InterruptedException, ExecutionException { latchRunnable.waitForDone(); this.get(); } public void done() { latchRunnable.done(); } } private static final class LatchRunnable implements Runnable { public final CountDownLatch startLatch = new CountDownLatch(1); public final CountDownLatch stopLatch = new CountDownLatch(1); public final CountDownLatch doneLatch = new CountDownLatch(1); public void waitForStart() throws InterruptedException { startLatch.await(); } public void waitForDone() throws InterruptedException { stopLatch.countDown(); doneLatch.await(); } public void done() { doneLatch.countDown(); } public void run() { startLatch.countDown(); try { System.out.println(Thread.currentThread().getName()); stopLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }