/* * Copyright Terracotta, Inc. * * Licensed 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.ehcache.impl.internal.executor; import org.junit.After; import org.junit.Assert; import org.junit.Test; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.ehcache.impl.internal.executor.ExecutorUtil.waitFor; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class PartitionedScheduledExecutorTest { private OutOfBandScheduledExecutor scheduler = new OutOfBandScheduledExecutor(); @After public void after() { scheduler.shutdownNow(); } @Test public void testShutdownOfIdleExecutor() throws InterruptedException { ExecutorService worker = Executors.newCachedThreadPool(); PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); executor.shutdown(); assertThat(executor.isShutdown(), is(true)); assertThat(executor.awaitTermination(2, TimeUnit.MINUTES), is(true)); assertThat(executor.isTerminated(), is(true)); } @Test public void testShutdownNowOfIdleExecutor() throws InterruptedException { ExecutorService worker = Executors.newCachedThreadPool(); PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); assertThat(executor.shutdownNow(), empty()); assertThat(executor.isShutdown(), is(true)); assertThat(executor.awaitTermination(2, TimeUnit.MINUTES), is(true)); assertThat(executor.isTerminated(), is(true)); } @Test public void testShutdownLeavesJobRunning() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); final Semaphore semaphore = new Semaphore(0); executor.execute(new Runnable() { @Override public void run() { semaphore.acquireUninterruptibly(); } }); executor.shutdown(); assertThat(executor.awaitTermination(100, MILLISECONDS), is(false)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(false)); semaphore.release(); assertThat(executor.awaitTermination(2, MINUTES), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(semaphore.availablePermits(), is(0)); } finally { worker.shutdown(); } } @Test public void testQueuedJobRunsAfterShutdown() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); final Semaphore jobSemaphore = new Semaphore(0); final Semaphore testSemaphore = new Semaphore(0); executor.submit(new Callable<Void>() { @Override public Void call() throws Exception { testSemaphore.release(); jobSemaphore.acquireUninterruptibly(); return null; } }); executor.submit(new Callable<Void>() { @Override public Void call() throws Exception { jobSemaphore.acquireUninterruptibly(); return null; } }); testSemaphore.acquireUninterruptibly(); executor.shutdown(); assertThat(executor.awaitTermination(100, MILLISECONDS), is(false)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(false)); jobSemaphore.release(); assertThat(executor.awaitTermination(100, MILLISECONDS), is(false)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(false)); jobSemaphore.release(); assertThat(executor.awaitTermination(2, MINUTES), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(jobSemaphore.availablePermits(), is(0)); } finally { worker.shutdown(); } } @Test public void testQueuedJobIsStoppedAfterShutdownNow() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); final Semaphore jobSemaphore = new Semaphore(0); final Semaphore testSemaphore = new Semaphore(0); executor.submit(new Callable<Void>() { @Override public Void call() throws Exception { testSemaphore.release(); jobSemaphore.acquireUninterruptibly(); return null; } }); final AtomicBoolean called = new AtomicBoolean(); Callable<?> leftBehind = new Callable<Void>() { @Override public Void call() throws Exception { called.set(true); return null; } }; executor.submit(leftBehind); testSemaphore.acquireUninterruptibly(); assertThat(executor.shutdownNow(), hasSize(1)); assertThat(executor.awaitTermination(100, MILLISECONDS), is(false)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(false)); jobSemaphore.release(); assertThat(executor.awaitTermination(2, MINUTES), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(jobSemaphore.availablePermits(), is(0)); assertThat(called.get(), is(false)); } finally { worker.shutdown(); } } @Test public void testRunningJobIsInterruptedAfterShutdownNow() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); final Semaphore jobSemaphore = new Semaphore(0); final Semaphore testSemaphore = new Semaphore(0); final AtomicBoolean interrupted = new AtomicBoolean(); executor.submit(new Callable<Void>() { @Override public Void call() throws Exception { testSemaphore.release(); try { jobSemaphore.acquire(); } catch (InterruptedException e) { interrupted.set(true); } return null; } }); testSemaphore.acquireUninterruptibly(); assertThat(executor.shutdownNow(), empty()); assertThat(executor.awaitTermination(2, MINUTES), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(jobSemaphore.availablePermits(), is(0)); assertThat(interrupted.get(), is(true)); } finally { worker.shutdown(); } } @Test public void testRunningJobsAreInterruptedAfterShutdownNow() throws InterruptedException { final int jobCount = 4; ExecutorService worker = Executors.newCachedThreadPool(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); final Semaphore jobSemaphore = new Semaphore(0); final Semaphore testSemaphore = new Semaphore(0); final AtomicInteger interrupted = new AtomicInteger(); for (int i = 0; i < jobCount; i++) { executor.submit(new Callable<Void>() { @Override public Void call() throws Exception { testSemaphore.release(); try { jobSemaphore.acquire(); } catch (InterruptedException e) { interrupted.incrementAndGet(); } return null; } }); } testSemaphore.acquireUninterruptibly(jobCount); assertThat(executor.shutdownNow(), empty()); assertThat(executor.awaitTermination(2, MINUTES), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(jobSemaphore.availablePermits(), is(0)); assertThat(interrupted.get(), is(jobCount)); } finally { worker.shutdown(); } } @Test public void testFixedRatePeriodicTaskIsCancelledByShutdown() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { Assert.fail("Should not run!"); } }, 2, 1, MINUTES); executor.shutdown(); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isCancelled(), is(true)); } finally { worker.shutdown(); } } @Test public void testFixedDelayPeriodicTaskIsCancelledByShutdown() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { Assert.fail("Should not run!"); } }, 2, 1, MINUTES); executor.shutdown(); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isCancelled(), is(true)); } finally { worker.shutdown(); } } @Test public void testFixedRatePeriodicTaskIsCancelledByShutdownNow() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { //no-op } }, 2, 1, MINUTES); assertThat(executor.shutdownNow(), hasSize(1)); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isDone(), is(false)); } finally { worker.shutdown(); } } @Test public void testFixedDelayPeriodicTaskIsRemovedByShutdownNow() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { Assert.fail("Should not run!"); } }, 2, 1, MINUTES); assertThat(executor.shutdownNow(), hasSize(1)); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isDone(), is(false)); } finally { worker.shutdown(); } } @Test public void testDelayedTaskIsRemovedByShutdownNow() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.schedule(new Runnable() { @Override public void run() { Assert.fail("Should not run!"); } }, 2, MINUTES); List<Runnable> remainingTasks = executor.shutdownNow(); assertThat(remainingTasks, hasSize(1)); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isDone(), is(false)); for (Runnable r : remainingTasks) r.run(); assertThat(future.isDone(), is(true)); } finally { worker.shutdown(); } } @Test public void testTerminationAfterShutdownWaitsForDelayedTask() throws InterruptedException { ExecutorService worker = Executors.newSingleThreadExecutor(); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<?> future = executor.schedule(new Runnable() { @Override public void run() { //no-op } }, 200, MILLISECONDS); executor.shutdown(); assertThat(executor.awaitTermination(30, SECONDS), is(true)); assertThat(executor.isShutdown(), is(true)); assertThat(executor.isTerminated(), is(true)); assertThat(future.isDone(), is(true)); } finally { worker.shutdown(); } } @Test public void testScheduledTasksRunOnDeclaredPool() throws InterruptedException, ExecutionException { ExecutorService worker = Executors.newSingleThreadExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "testScheduledTasksRunOnDeclaredPool"); } }); try { PartitionedScheduledExecutor executor = new PartitionedScheduledExecutor(scheduler, worker); ScheduledFuture<Thread> future = executor.schedule(new Callable<Thread>() { @Override public Thread call() { return Thread.currentThread(); } }, 0, MILLISECONDS); assertThat(waitFor(future).getName(), is("testScheduledTasksRunOnDeclaredPool")); executor.shutdown(); } finally { worker.shutdown(); } } }