/*
* Copyright 2010-2013 Ning, Inc.
* Copyright 2014-2017 Groupon, Inc
* Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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 org.killbill.commons.concurrent;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.WriterAppender;
import org.testng.Assert;
import org.testng.annotations.Test;
@Test(singleThreaded = true)
public class TestExecutors {
private void registerAppenders(final Logger loggingLogger, final Logger failsafeLogger, final WriterAppender dummyAppender) {
dummyAppender.setImmediateFlush(true);
loggingLogger.setLevel(Level.DEBUG);
failsafeLogger.setLevel(Level.DEBUG);
loggingLogger.addAppender(dummyAppender);
failsafeLogger.addAppender(dummyAppender);
}
private void unregisterAppenders(final ExecutorService executorService, final Logger loggingLogger, final Logger failsafeLogger, final WriterAppender dummyAppender) throws InterruptedException {
executorService.shutdown();
Assert.assertTrue(executorService.isShutdown());
Assert.assertTrue(executorService.awaitTermination(10, TimeUnit.SECONDS));
Assert.assertEquals(executorService.shutdownNow().size(), 0);
Assert.assertTrue(executorService.isTerminated());
loggingLogger.removeAppender(dummyAppender);
failsafeLogger.removeAppender(dummyAppender);
}
private void runtimeTest(final ExecutorService executorService) throws Exception {
final Logger loggingLogger = Logger.getLogger(LoggingExecutor.class);
final Logger failsafeLogger = Logger.getLogger(FailsafeScheduledExecutor.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final WriterAppender dummyAppender = new WriterAppender(new SimpleLayout(), bos);
registerAppenders(loggingLogger, failsafeLogger, dummyAppender);
Future<?> future = executorService.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("Fail!");
}
});
try {
future.get();
Assert.fail("Expected exception");
} catch (final ExecutionException e) {
Assert.assertEquals(e.getCause().toString(), "java.lang.RuntimeException: Fail!");
}
future = executorService.submit(new Runnable() {
@Override
public void run() {
}
}, "bright");
Assert.assertEquals(future.get(), "bright");
future = executorService.submit(new Runnable() {
@Override
public void run() {
throw new RuntimeException("Again!");
}
}, "Unimportant");
try {
future.get();
Assert.fail("Expected exception");
} catch (final ExecutionException e) {
Assert.assertEquals(e.getCause().toString(), "java.lang.RuntimeException: Again!");
}
unregisterAppenders(executorService, loggingLogger, failsafeLogger, dummyAppender);
final String actual = bos.toString();
assertPattern(actual, Pattern.compile("^ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.RuntimeException: Fail!\r?\n"));
assertPattern(actual, Pattern.compile("DEBUG - Thread\\[TestLoggingExecutor-[^\\]]+\\] finished executing$"));
}
private void errorTest(final ExecutorService executorService) throws Exception {
final Logger loggingLogger = Logger.getLogger(LoggingExecutor.class);
final Logger failsafeLogger = Logger.getLogger(FailsafeScheduledExecutor.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final WriterAppender dummyAppender = new WriterAppender(new SimpleLayout(), bos);
registerAppenders(loggingLogger, failsafeLogger, dummyAppender);
executorService.execute(new Runnable() {
@Override
public void run() {
throw new OutOfMemoryError("Poof!");
}
});
unregisterAppenders(executorService, loggingLogger, failsafeLogger, dummyAppender);
final String actual = bos.toString();
assertPattern(actual, Pattern.compile("^ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.OutOfMemoryError: Poof!\r?\n"));
assertPattern(actual, Pattern.compile("DEBUG - Thread\\[TestLoggingExecutor-[^\\]]+\\] finished executing$"));
}
private void callableTest(final ExecutorService executorService) throws Exception {
final Logger loggingLogger = Logger.getLogger(LoggingExecutor.class);
final Logger failsafeLogger = Logger.getLogger(FailsafeScheduledExecutor.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final WriterAppender dummyAppender = new WriterAppender(new SimpleLayout(), bos);
registerAppenders(loggingLogger, failsafeLogger, dummyAppender);
Future<?> future = executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
throw new Exception("Oops!");
}
});
try {
future.get();
Assert.fail("Expected exception");
} catch (final ExecutionException e) {
Assert.assertEquals(e.getCause().toString(), "java.lang.Exception: Oops!");
}
future = executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
throw new OutOfMemoryError("Uh oh!");
}
});
try {
future.get();
Assert.fail("Expected exception");
} catch (final ExecutionException e) {
Assert.assertEquals(e.getCause().toString(), "java.lang.OutOfMemoryError: Uh oh!");
}
unregisterAppenders(executorService, loggingLogger, failsafeLogger, dummyAppender);
final String actual = bos.toString();
assertPattern(actual, Pattern.compile("DEBUG - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended with an exception\r?\njava.lang.Exception: Oops!\r?\n"));
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended with an exception\r?\njava.lang.OutOfMemoryError: Uh oh!\r?\n"));
assertPattern(actual, Pattern.compile("DEBUG - Thread\\[TestLoggingExecutor-[^\\]]+\\] finished executing$"));
}
private void scheduledTest(final ScheduledExecutorService executorService) throws Exception {
final Logger loggingLogger = Logger.getLogger(LoggingExecutor.class);
final Logger failsafeLogger = Logger.getLogger(FailsafeScheduledExecutor.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final WriterAppender dummyAppender = new WriterAppender(new SimpleLayout(), bos);
registerAppenders(loggingLogger, failsafeLogger, dummyAppender);
final CountDownLatch scheduleLatch = new CountDownLatch(1);
final CountDownLatch fixedDelayLatch = new CountDownLatch(2);
final CountDownLatch fixedRateLatch = new CountDownLatch(2);
final Future<?> future = executorService.schedule(new Callable<Void>() {
@Override
public Void call() throws Exception {
throw new Exception("Pow!");
}
}, 1, TimeUnit.MILLISECONDS);
executorService.schedule(new Runnable() {
@Override
public void run() {
scheduleLatch.countDown();
throw new RuntimeException("D'oh!");
}
}, 1, TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
fixedDelayLatch.countDown();
if (fixedDelayLatch.getCount() != 0) {
throw new OutOfMemoryError("Zoinks!");
} else {
throw new RuntimeException("Eep!");
}
}
}, 1, 1, TimeUnit.MILLISECONDS);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
fixedRateLatch.countDown();
if (fixedRateLatch.getCount() != 0) {
throw new OutOfMemoryError("Zounds!");
} else {
throw new RuntimeException("Egad!");
}
}
}, 1, 1, TimeUnit.MILLISECONDS);
try {
future.get();
Assert.fail("Expected exception");
} catch (final ExecutionException e) {
Assert.assertEquals(e.getCause().toString(), "java.lang.Exception: Pow!");
}
scheduleLatch.await();
fixedDelayLatch.await();
fixedRateLatch.await();
unregisterAppenders(executorService, loggingLogger, failsafeLogger, dummyAppender);
final String actual = bos.toString();
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.RuntimeException: D'oh!\r?\n"));
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.OutOfMemoryError: Zoinks!\r?\n"));
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.RuntimeException: Eep!\r?\n"));
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.OutOfMemoryError: Zounds!\r?\n"));
assertPattern(actual, Pattern.compile("ERROR - Thread\\[TestLoggingExecutor-[^\\]]+\\] ended abnormally with an exception\r?\njava.lang.RuntimeException: Egad!\r?\n"));
assertPattern(actual, Pattern.compile("DEBUG - Thread\\[TestLoggingExecutor-[^\\]]+\\] finished executing$"));
}
private void assertPattern(final String actual, final Pattern expected) {
Assert.assertTrue(expected.matcher(actual).find(), String.format("Expected to see:\n%s\nin:\n%s", indent(expected.toString()), indent(actual)));
}
private String indent(final String str) {
return "\t" + str.replaceAll("\n", "\n\t");
}
@Test(groups = "fast")
public void testSingleThreadExecutorRuntimeException() throws Exception {
runtimeTest(Executors.newSingleThreadExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadExecutorError() throws Exception {
errorTest(Executors.newSingleThreadExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadExecutorCallable() throws Exception {
callableTest(Executors.newSingleThreadExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testCachedThreadPoolRuntimeException() throws Exception {
runtimeTest(Executors.newCachedThreadPool("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testCachedThreadPoolError() throws Exception {
errorTest(Executors.newCachedThreadPool("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testCachedThreadPoolCallable() throws Exception {
callableTest(Executors.newCachedThreadPool("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testFixedThreadPoolRuntimeException() throws Exception {
runtimeTest(Executors.newFixedThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testFixedThreadPoolError() throws Exception {
errorTest(Executors.newFixedThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testFixedThreadPoolCallable() throws Exception {
callableTest(Executors.newFixedThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testScheduledThreadPoolRuntimeException() throws Exception {
runtimeTest(Executors.newScheduledThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testScheduledThreadPoolError() throws Exception {
errorTest(Executors.newScheduledThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testScheduledThreadPoolCallable() throws Exception {
callableTest(Executors.newScheduledThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testScheduledThreadPoolScheduled() throws Exception {
scheduledTest(Executors.newScheduledThreadPool(10, "TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadScheduledExecutorRuntimeException() throws Exception {
runtimeTest(Executors.newSingleThreadScheduledExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadScheduledExecutorError() throws Exception {
errorTest(Executors.newSingleThreadScheduledExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadScheduledExecutorCallable() throws Exception {
callableTest(Executors.newSingleThreadScheduledExecutor("TestLoggingExecutor"));
}
@Test(groups = "fast")
public void testSingleThreadScheduledExecutorScheduled() throws Exception {
scheduledTest(Executors.newSingleThreadScheduledExecutor("TestLoggingExecutor"));
}
}