/* 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);
}
}
}