/* * 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import java.util.concurrent.atomic.AtomicInteger; 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; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; public class PartitionedOrderedExecutorTest { @Test public void testShutdownOfIdleExecutor() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = mock(ExecutorService.class); PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = mock(ExecutorService.class); PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 testTerminatedExecutorRejectsJob() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = mock(ExecutorService.class); PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); executor.shutdown(); assertThat(executor.awaitTermination(2, TimeUnit.MINUTES), is(true)); try { executor.execute(new Runnable() { @Override public void run() { //no-op } }); fail("Expected RejectedExecutionException"); } catch (RejectedExecutionException e) { //expected } } @Test public void testShutdownButNonTerminatedExecutorRejectsJob() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newSingleThreadExecutor(); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); final Semaphore semaphore = new Semaphore(0); executor.execute(new Runnable() { @Override public void run() { semaphore.acquireUninterruptibly(); } }); executor.shutdown(); try { executor.execute(new Runnable() { @Override public void run() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }); fail("Expected RejectedExecutionException"); } catch (RejectedExecutionException e) { //expected } semaphore.release(); assertThat(executor.awaitTermination(2, MINUTES), is(true)); } finally { service.shutdown(); } } @Test public void testShutdownLeavesJobRunning() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newSingleThreadExecutor(); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 { service.shutdown(); } } @Test public void testQueuedJobRunsAfterShutdown() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newSingleThreadExecutor(); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 { service.shutdown(); } } @Test public void testQueuedJobIsStoppedAfterShutdownNow() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newSingleThreadExecutor(); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 { service.shutdown(); } } @Test public void testRunningJobIsInterruptedAfterShutdownNow() throws InterruptedException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newSingleThreadExecutor(); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); 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 { service.shutdown(); } } @Test public void testJobsAreExecutedInOrder() throws InterruptedException, ExecutionException { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); ExecutorService service = Executors.newFixedThreadPool(2); try { PartitionedOrderedExecutor executor = new PartitionedOrderedExecutor(queue, service); final AtomicInteger sequence = new AtomicInteger(-1); List<Future<?>> tasks = new ArrayList<Future<?>>(); for (int i = 0; i < 100; i++) { final int index = i; tasks.add(executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { assertThat(sequence.getAndSet(index), is(index -1)); return null; } })); } for (Future<?> task : tasks) { task.get(); } } finally { service.shutdown(); } } }