/* Copyright (c) 2011 Danish Maritime Authority * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2000-2013 Heinz Max Kabutz * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Heinz Max Kabutz 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 * * 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 dk.dma.ais.concurrency.stripedexecutor; import eu.javaspecialists.tjsn.concurrency.StripedCallable; import eu.javaspecialists.tjsn.concurrency.StripedRunnable; import org.junit.Before; import org.junit.Test; import java.util.Collection; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author Heinz Kabutz */ public class StripedExecutorServiceTest { @Before public void initialize() { TestRunnable.outOfSequence = TestUnstripedRunnable.outOfSequence = TestFastRunnable.outOfSequence = false; } @Test public void testSingleStripeRunnable() throws InterruptedException { ExecutorService pool = new StripedExecutorService(); Object stripe = new Object(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestRunnable(stripe, actual, i)); } assertFalse(pool.isTerminated()); assertFalse(pool.isShutdown()); pool.shutdown(); assertTrue(pool.awaitTermination(1, TimeUnit.HOURS)); assertFalse("Expected no out-of-sequence runnables to execute", TestRunnable.outOfSequence); assertTrue(pool.isTerminated()); } @Test public void testShutdown() throws InterruptedException { ThreadGroup group = new ThreadGroup("stripetestgroup"); Thread starter = new Thread(group, "starter") { public void run() { ExecutorService pool = new StripedExecutorService(); Object stripe = new Object(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestRunnable(stripe, actual, i)); } pool.shutdown(); } }; starter.start(); starter.join(); for (int i = 0; i < 100; i++) { if (group.activeCount() == 0) { return; } Thread.sleep(100); } assertEquals(0, group.activeCount()); } @Test public void testShutdownNow() throws InterruptedException { ExecutorService pool = new StripedExecutorService(); Object stripe = new Object(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestRunnable(stripe, actual, i)); } Thread.sleep(500); assertFalse(pool.isTerminated()); Collection<Runnable> unfinishedJobs = pool.shutdownNow(); assertTrue(pool.isShutdown()); assertTrue(pool.awaitTermination(1, TimeUnit.MINUTES)); assertTrue(pool.isTerminated()); assertTrue(unfinishedJobs.size() > 0); assertEquals(100, unfinishedJobs.size() + actual.intValue()); } @Test public void testSingleStripeCallableWithCompletionService() throws InterruptedException, ExecutionException { ExecutorService pool = new StripedExecutorService(); final CompletionService<Integer> cs = new ExecutorCompletionService<>( pool ); Thread testSubmitter = new Thread("TestSubmitter") { public void run() { Object stripe = new Object(); for (int i = 0; i < 50; i++) { cs.submit(new TestCallable(stripe, i)); } try { Thread.sleep(2000); } catch (InterruptedException e) { interrupt(); } for (int i = 50; i < 100; i++) { cs.submit(new TestCallable(stripe, i)); } } }; testSubmitter.start(); for (int i = 0; i < 100; i++) { int actual = cs.take().get().intValue(); //System.out.println("Retrieved " + actual); assertEquals(i, actual); } pool.shutdown(); assertTrue(pool.awaitTermination(1, TimeUnit.HOURS)); testSubmitter.join(); } @Test public void testUnstripedRunnable() throws InterruptedException { ExecutorService pool = new StripedExecutorService(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestUnstripedRunnable(actual, i)); } pool.shutdown(); assertTrue(pool.awaitTermination(1, TimeUnit.HOURS)); assertTrue("Expected at least some out-of-sequence runnables to execute", TestUnstripedRunnable.outOfSequence); } @Test public void testMultipleStripes() throws InterruptedException { final ExecutorService pool = new StripedExecutorService(); ExecutorService producerPool = Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { producerPool.submit(new Runnable() { public void run() { Object stripe = new Object(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestRunnable(stripe, actual, i)); } } }); } producerPool.shutdown(); while (!producerPool.awaitTermination(1, TimeUnit.MINUTES)) { System.out.print("."); // checkstyle } pool.shutdown(); assertTrue(pool.awaitTermination(1, TimeUnit.DAYS)); assertFalse("Expected no out-of-sequence runnables to execute", TestRunnable.outOfSequence); } @Test public void testMultipleFastStripes() throws InterruptedException { final ExecutorService pool = new StripedExecutorService(); ExecutorService producerPool = Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { producerPool.submit(new Runnable() { public void run() { Object stripe = new Object(); AtomicInteger actual = new AtomicInteger(0); for (int i = 0; i < 100; i++) { pool.submit(new TestFastRunnable(stripe, actual, i)); } } }); } producerPool.shutdown(); while (!producerPool.awaitTermination(1, TimeUnit.MINUTES)) { System.out.print("."); // checkstyle } pool.shutdown(); assertTrue(pool.awaitTermination(1, TimeUnit.DAYS)); assertFalse("Expected no out-of-sequence runnables to execute", TestFastRunnable.outOfSequence); } public static class TestRunnable implements StripedRunnable { private final Object stripe; private final AtomicInteger stripeSequence; private final int expected; private static volatile boolean outOfSequence; // = false; public TestRunnable(Object stripe, AtomicInteger stripeSequence, int expected) { this.stripe = stripe; this.stripeSequence = stripeSequence; this.expected = expected; } public Object getStripe() { return stripe; } public void run() { try { ThreadLocalRandom rand = ThreadLocalRandom.current(); Thread.sleep(rand.nextInt(10) + 10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } int actual = stripeSequence.getAndIncrement(); if (actual != expected) { outOfSequence = true; } //System.out.printf("Execute strip %h %d %d%n", stripe, actual, expected); assertEquals("out of sequence", actual, expected); } } public static class TestFastRunnable implements StripedRunnable { private final Object stripe; private final AtomicInteger stripeSequence; private final int expected; private static volatile boolean outOfSequence; // = false; public TestFastRunnable(Object stripe, AtomicInteger stripeSequence, int expected) { this.stripe = stripe; this.stripeSequence = stripeSequence; this.expected = expected; } public Object getStripe() { return stripe; } public void run() { int actual = stripeSequence.getAndIncrement(); if (actual != expected) { outOfSequence = true; } //System.out.printf("Execute strip %h %d %d%n", stripe, actual, expected); assertEquals("out of sequence", actual, expected); } } public static class TestCallable implements StripedCallable<Integer> { private final Object stripe; private final int expected; public TestCallable(Object stripe, int expected) { this.stripe = stripe; this.expected = expected; } public Object getStripe() { return stripe; } public Integer call() throws Exception { try { ThreadLocalRandom rand = ThreadLocalRandom.current(); Thread.sleep(rand.nextInt(10) + 10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return expected; } } public static class TestUnstripedRunnable implements Runnable { private final AtomicInteger stripeSequence; private final int expected; private static volatile boolean outOfSequence; // = false; public TestUnstripedRunnable(AtomicInteger stripeSequence, int expected) { this.stripeSequence = stripeSequence; this.expected = expected; } public void run() { try { ThreadLocalRandom rand = ThreadLocalRandom.current(); Thread.sleep(rand.nextInt(10) + 10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } int actual = stripeSequence.getAndIncrement(); if (actual != expected) { outOfSequence = true; } //System.out.println("Execute unstriped " + actual + ", " + expected); } } }