Docs for ExecutorService#close and canceled tasks
Fabian Meumertzheim
fabian at buildbuddy.io
Sun Jan 5 10:20:06 UTC 2025
Hi,
I recently traced a race in an application
(https://github.com/bazelbuild/bazel/issues/21773) down to a
particular behavior of ExecutorService#close that, to me, doesn't seem
to be obvious from its documentation: If a task that has been
submitted to the executor is canceled while it is already executing,
ExecutorService#close will not wait for the associated Runnable to
return.
Consider the following example:
var taskRunning = new AtomicBoolean(true);
try (var executorService = Executors.newVirtualThreadPerTaskExecutor()) {
var taskStarted = new CountDownLatch(1);
var task =
executorService.submit(
() -> {
// Uninterruptibly wait for a second.
taskStarted.countDown();
long end = System.currentTimeMillis() + 1000;
long remaining;
while ((remaining = end - System.currentTimeMillis()) > 0) {
try {
Thread.sleep(remaining);
} catch (InterruptedException e) {
}
}
taskRunning.set(false);
});
// Cancel the task after it has started execution.
try {
taskStarted.await();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
task.cancel(false);
}
System.err.println("Task still running: " + taskRunning.get());
This will print "Task still running: true" and exit immediately
instead of waiting for a second.
It would have been helpful to me if the phrase "completed execution"
in the docs for #awaitTermination and #close had mentioned that
canceled tasks are always considered to have completed execution, even
if their Runnable hasn't returned yet.
Would a change that more clearly documents this behavior be welcome?
Is there a clear definition of "completed execution" in some other
parts of the j.u.c docs that specifies this behavior?
Fabian
More information about the core-libs-dev
mailing list