-
Bug
-
Resolution: Unresolved
-
P3
-
7, 8u144, 9
-
Cause Known
FULL PRODUCT VERSION :
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux 4.10.0-30-generic #34~16.04.1-Ubuntu SMP Wed Aug 2 02:13:56 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
In some cases when a task if successfully submitted to a ThreadPoolExecutor and the shutdown method is called immediately after the executor fails to run the submitted task and never reaches the terminated state. This happens when another call to execute on this ThreadPoolExecutor occurs asynchronously, overlapping the call to shutdown. The overlapping execute call ends up being rejected as the ThreadPoolExecutor has moved to the shutdown state.
This seems to happen only in the case where the executor has no active worker threads and the initial successful execute call has initiated creation of a worker thread but that worker has not been started yet when the overlapping shutdown and execute calls occur. In this case the worker thread appears to be created but never gets started and seems to be dropped.
At the awaitTermination point the caller waits and eventually times out as the pool never moves into that state and it retains the successfully submitted task in it's queue but will never run it as no new worker is ever spawned.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The included test is able to reproduce this fairly repeatable on my machine, on some others it doesn't show up as readily.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected result is that the executor honors the contract of the shutdown method and any previously successful task submissions are run prior to the executor eventually reaching the termination state.
ACTUAL -
The executor never runs the task submitted and never reaches the termination state following the shutdown call.
REPRODUCIBILITY :
This bug can be reproduced occasionally.
---------- BEGIN SOURCE ----------
import static org.junit.Assert.fail;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorTest {
public static void main(String...args) throws Exception {
for (int i = 0; i < 100000; ++i) {
final int iteration = i;
System.out.println("Running test iteration: " + i);
final ThreadPoolExecutor service = createThreadPoolExecutor("Service:" + i);
final ThreadPoolExecutor asyncRunner = createThreadPoolExecutor("Async:" + i);
// Queue a task that asynchronously enqueues onto the service
try {
asyncRunner.execute(() -> {
Thread.yield();
// Task to circle back to the main service executor
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("Async enqueued task now running");
}
@Override
public String toString() {
return "Async-r" + iteration;
}
});
});
} catch (RejectedExecutionException ex) {
System.out.println("Async enqueued task was rejected from the executor.");
}
service.prestartCoreThread();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("Service enqueued task " + iteration +" running.");
}
@Override
public String toString() {
return "Service-r" + iteration;
}
});
Thread.yield();
service.shutdown();
try {
if (!service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Service failed to reach termination state");
System.out.println("Service Excecutor: pool size: " + service.getPoolSize());
System.out.println("Service Excecutor: task count: " + service.getTaskCount());
System.out.println("Service Excecutor: completed task count: " + service.getCompletedTaskCount());
System.out.println("Service Excecutor: remaining task: " + service.getQueue().peek());
fail("Service did not reach termination");
}
} finally {
asyncRunner.shutdown();
}
}
}
private static ThreadPoolExecutor createThreadPoolExecutor(String threadName) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new TestThreadFactory(threadName));
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
System.out.println("task was rejected from the executor: " + r);
super.rejectedExecution(r, executor);
}
});
return executor;
}
public static class TestThreadFactory implements ThreadFactory {
private final String threadName;
private final boolean wrapTask;
private final boolean daemon;
public TestThreadFactory(String threadName) {
this.threadName = threadName;
this.wrapTask = false;
this.daemon = true;
}
public TestThreadFactory(String threadName, boolean wrapTask) {
this.threadName = threadName;
this.wrapTask = wrapTask;
this.daemon = true;
}
public TestThreadFactory(String threadName, boolean wrapTask, boolean daemon) {
this.threadName = threadName;
this.wrapTask = wrapTask;
this.daemon = daemon;
}
@Override
public Thread newThread(final Runnable target) {
Runnable runner = target;
System.out.println("Creating new Thread with name: " + threadName + "-t");
if (wrapTask) {
runner = new Runnable() {
@Override
public void run() {
System.out.println("Running executor task: " + target);
try {
target.run();
} catch (Throwable t) {
System.out.println("Executor task threw exception: " + t.getClass().getSimpleName());
throw t;
} finally {
System.out.println("Run of executor task: " + target + " complete");
}
}
};
}
Thread thread = new Thread(runner, threadName + "-t");
thread.setDaemon(daemon);
return thread;
}
}
}
---------- END SOURCE ----------
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux 4.10.0-30-generic #34~16.04.1-Ubuntu SMP Wed Aug 2 02:13:56 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
In some cases when a task if successfully submitted to a ThreadPoolExecutor and the shutdown method is called immediately after the executor fails to run the submitted task and never reaches the terminated state. This happens when another call to execute on this ThreadPoolExecutor occurs asynchronously, overlapping the call to shutdown. The overlapping execute call ends up being rejected as the ThreadPoolExecutor has moved to the shutdown state.
This seems to happen only in the case where the executor has no active worker threads and the initial successful execute call has initiated creation of a worker thread but that worker has not been started yet when the overlapping shutdown and execute calls occur. In this case the worker thread appears to be created but never gets started and seems to be dropped.
At the awaitTermination point the caller waits and eventually times out as the pool never moves into that state and it retains the successfully submitted task in it's queue but will never run it as no new worker is ever spawned.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The included test is able to reproduce this fairly repeatable on my machine, on some others it doesn't show up as readily.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected result is that the executor honors the contract of the shutdown method and any previously successful task submissions are run prior to the executor eventually reaching the termination state.
ACTUAL -
The executor never runs the task submitted and never reaches the termination state following the shutdown call.
REPRODUCIBILITY :
This bug can be reproduced occasionally.
---------- BEGIN SOURCE ----------
import static org.junit.Assert.fail;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorTest {
public static void main(String...args) throws Exception {
for (int i = 0; i < 100000; ++i) {
final int iteration = i;
System.out.println("Running test iteration: " + i);
final ThreadPoolExecutor service = createThreadPoolExecutor("Service:" + i);
final ThreadPoolExecutor asyncRunner = createThreadPoolExecutor("Async:" + i);
// Queue a task that asynchronously enqueues onto the service
try {
asyncRunner.execute(() -> {
Thread.yield();
// Task to circle back to the main service executor
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("Async enqueued task now running");
}
@Override
public String toString() {
return "Async-r" + iteration;
}
});
});
} catch (RejectedExecutionException ex) {
System.out.println("Async enqueued task was rejected from the executor.");
}
service.prestartCoreThread();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("Service enqueued task " + iteration +" running.");
}
@Override
public String toString() {
return "Service-r" + iteration;
}
});
Thread.yield();
service.shutdown();
try {
if (!service.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Service failed to reach termination state");
System.out.println("Service Excecutor: pool size: " + service.getPoolSize());
System.out.println("Service Excecutor: task count: " + service.getTaskCount());
System.out.println("Service Excecutor: completed task count: " + service.getCompletedTaskCount());
System.out.println("Service Excecutor: remaining task: " + service.getQueue().peek());
fail("Service did not reach termination");
}
} finally {
asyncRunner.shutdown();
}
}
}
private static ThreadPoolExecutor createThreadPoolExecutor(String threadName) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), new TestThreadFactory(threadName));
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
System.out.println("task was rejected from the executor: " + r);
super.rejectedExecution(r, executor);
}
});
return executor;
}
public static class TestThreadFactory implements ThreadFactory {
private final String threadName;
private final boolean wrapTask;
private final boolean daemon;
public TestThreadFactory(String threadName) {
this.threadName = threadName;
this.wrapTask = false;
this.daemon = true;
}
public TestThreadFactory(String threadName, boolean wrapTask) {
this.threadName = threadName;
this.wrapTask = wrapTask;
this.daemon = true;
}
public TestThreadFactory(String threadName, boolean wrapTask, boolean daemon) {
this.threadName = threadName;
this.wrapTask = wrapTask;
this.daemon = daemon;
}
@Override
public Thread newThread(final Runnable target) {
Runnable runner = target;
System.out.println("Creating new Thread with name: " + threadName + "-t");
if (wrapTask) {
runner = new Runnable() {
@Override
public void run() {
System.out.println("Running executor task: " + target);
try {
target.run();
} catch (Throwable t) {
System.out.println("Executor task threw exception: " + t.getClass().getSimpleName());
throw t;
} finally {
System.out.println("Run of executor task: " + target + " complete");
}
}
};
}
Thread thread = new Thread(runner, threadName + "-t");
thread.setDaemon(daemon);
return thread;
}
}
}
---------- END SOURCE ----------