-
Bug
-
Resolution: Won't Fix
-
P5
-
None
-
8, 9
-
generic
-
generic
FULL PRODUCT VERSION :
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
We have developed a batch processing tool for transferring a large number of documents from one content management system to another. In order to increase throughput we make use of a thread-pool-executor which executes several migration tasks in parallel. In case of an unexpected exception our application should notify a human administrator by sending an e-mail. We wanted to achieve this by means of an uncaught-exception-handler that gets called whenever a runtime exception or JVM error occurs.
Unfortunately the thread-pool-executor - when it is shut down - does not wait for a pool thread to terminate if that thread is currently in the state of running the uncaught-exception-handler. So the bug is that the thread-pool-executor reports that is has been terminated successfully although a pool thread is still occupied with uncaught exception handling. In our scenario this misbehaviour caused a premature termination of the whole application preventing the expected notification e-mail from being delivered!!!
It was very hard to figure out why our application did not behave in the expected way!!!
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I have constructed a JUnit test case for reproducing the problem.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
We expect that all pool threads have been terminated if method awaitTermination() returns true.
ACTUAL -
But this is not guaranteed if one of the pool threads is in the state of running an uncaught-exception-handler.
REPRODUCIBILITY :
This bug can be reproduced occasionally.
---------- BEGIN SOURCE ----------
package de.cenit.test;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Test;
public class UncaughtExceptionHandlerTest {
private static final int THREAD_POOL_SIZE = 5;
private volatile boolean mailSent;
// We use an uncaught-exception-handler for notifying an administrator of unexpected exceptions.
private final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
System.out.printf("Thread %s is sending mail...\n", Thread.currentThread().getName());
// Instead of sending an e-mail we do some useless stuff here.
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
mailSent = true;
}
};
private final ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "test-" + threadNumber.getAndIncrement());
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return t;
}
};
@Test
public void testUncaughtExceptionHandler() throws InterruptedException {
final ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
executor.execute(new Runnable() {
@Override
public void run() {
// Throwing an exception here will result in an invocation of the uncaught-exception-handler.
throw new RuntimeException("Test exception!");
}
});
executor.shutdown();
Assert.assertTrue("A task is still running!", executor.awaitTermination(30, TimeUnit.SECONDS));
Assert.assertTrue("JVM terminates without having sent the expected e-mail!", mailSent);
}
}
---------- END SOURCE ----------
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
We have developed a batch processing tool for transferring a large number of documents from one content management system to another. In order to increase throughput we make use of a thread-pool-executor which executes several migration tasks in parallel. In case of an unexpected exception our application should notify a human administrator by sending an e-mail. We wanted to achieve this by means of an uncaught-exception-handler that gets called whenever a runtime exception or JVM error occurs.
Unfortunately the thread-pool-executor - when it is shut down - does not wait for a pool thread to terminate if that thread is currently in the state of running the uncaught-exception-handler. So the bug is that the thread-pool-executor reports that is has been terminated successfully although a pool thread is still occupied with uncaught exception handling. In our scenario this misbehaviour caused a premature termination of the whole application preventing the expected notification e-mail from being delivered!!!
It was very hard to figure out why our application did not behave in the expected way!!!
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I have constructed a JUnit test case for reproducing the problem.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
We expect that all pool threads have been terminated if method awaitTermination() returns true.
ACTUAL -
But this is not guaranteed if one of the pool threads is in the state of running an uncaught-exception-handler.
REPRODUCIBILITY :
This bug can be reproduced occasionally.
---------- BEGIN SOURCE ----------
package de.cenit.test;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Test;
public class UncaughtExceptionHandlerTest {
private static final int THREAD_POOL_SIZE = 5;
private volatile boolean mailSent;
// We use an uncaught-exception-handler for notifying an administrator of unexpected exceptions.
private final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
System.out.printf("Thread %s is sending mail...\n", Thread.currentThread().getName());
// Instead of sending an e-mail we do some useless stuff here.
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
mailSent = true;
}
};
private final ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "test-" + threadNumber.getAndIncrement());
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return t;
}
};
@Test
public void testUncaughtExceptionHandler() throws InterruptedException {
final ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
executor.execute(new Runnable() {
@Override
public void run() {
// Throwing an exception here will result in an invocation of the uncaught-exception-handler.
throw new RuntimeException("Test exception!");
}
});
executor.shutdown();
Assert.assertTrue("A task is still running!", executor.awaitTermination(30, TimeUnit.SECONDS));
Assert.assertTrue("JVM terminates without having sent the expected e-mail!", mailSent);
}
}
---------- END SOURCE ----------