javamultithreadingfutureexecutorservicecompletable-future

Interrupting a thread that's running a CompletableFuture


Consider I have an ExecutorService operating on top of a pool of daemon threads (so that I don't need to explicitly shut it down):

final var executor = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors(),
        r -> {
            final var t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
);

— and I want to cancel a long-running Future task after a certain time-out:

final var future = executor.submit(() -> {
    try {
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
        return "42";
    } catch (final InterruptedException ie) {
        System.out.println("Task cancelled.");
        throw ie;
    }
});

try {
    System.out.println(future.get(5L, TimeUnit.SECONDS));
} catch (final TimeoutException ignored) {
    System.out.println("Timed out, cancelling...");
    future.cancel(true);
}

When run, this code will produce the following output within about 5 seconds:

Timed out, cancelling...
Task cancelled.

This means that the thread currently running the task will indeed be interrupted, and the Thread.sleep() will throw an InterruptedException. Now, consider I want to use the CompletableFuture API instead. Here's the semantically identical code:

final var future = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
        return "42";
    } catch (final InterruptedException ie) {
        System.out.println("Task cancelled.");
        throw new RuntimeException(ie);
    }
}, executor);

Or so I thought. The 2nd version of the code, however, will only produce the following output:

Timed out, cancelling...

The background executor thread will continue running Thread.sleep(), without ever being interrupted, so these two versions of the code are not semantically the same.

Similarly, if I invoke future.orTimeout(timeout, unit) instead of future.get(timeout, unit), the background thread will not get interrupted, either. Background threads will continue running (potentially) computationally expensive tasks till the JVM is shut down, and there seems no way to interrupt them.

The only way to interrupt background threads is to call executor.shutdownNow() (mere executor.shutdown() is insufficient), but of course this is not a solution, as I want to reuse the executor after task cancellation.

Questions:

  1. How do I cancel a CompletableFuture and interrupt the corresponding worker thread, and
  2. is this behaviour documented anywhere?

Edit: there's a similar question: How to cancel Java 8 completable future?


Solution

  • The completable future API doesn't expect you to control the flow of your program using interrupts.

    The method CompletableFuture#cancel accepts a mayInterruptIfRunning argument, but the api docs state

    mayInterruptIfRunning - this value has no effect in this implementation because interrupts are not used to control processing.

    Essentially anything waiting on that future will stop waiting due to an exception. The task itself doesn't know that though.