Johnny
Johnny

Reputation: 7321

Shouldn't CompletableFuture.get(timeout) kill the task?

I'm running two instances of CompletableFuture, which wait 1 second and print something to the console. The first one I interrupt after 0.5 seconds. So I expect only the second one to print, but in fact both do. What's going on here?

Here's the code:

CompletableFuture<Void> c1 = CompletableFuture.runAsync(() -> {
    System.out.println("Start CF 1");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(1);
});

CompletableFuture<Void> c2 = CompletableFuture.runAsync(() -> {
    System.out.println("Start CF 2");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(2);
});

long start = System.currentTimeMillis();
try {
    c1.get(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
    System.out.println("CF interrupted after " + (System.currentTimeMillis() - start) + "ms");
}
c2.get();

And it prints:

Start CF 1
Start CF 2
CF interrupted after 510ms
2
1

Upvotes: 0

Views: 2575

Answers (2)

Holger
Holger

Reputation: 298153

The timeout for the get method only tells, when the execution of the get method should be stopped. It does not affect the computation of the result. This applies to all futures.

So, to interrupt a task, you would need to call cancel(true) but in case of CompletableFuture not even that will interrupt the task:

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

If you want interruptible tasks, you need to use an ExecutorService, but you can also use the same thread pool as CompletableFuture’s default executor:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService es = ForkJoinPool.commonPool();
    Future<Void> c1 = es.submit(() -> {
        System.out.println("Start CF 1");
        Thread.sleep(1000);
        System.out.println(1);
        return null;
    });

    Future<Void> c2 = es.submit(() -> {
        System.out.println("Start CF 2");
        Thread.sleep(1000);
        System.out.println(2);
        return null;
    });

    long start = System.currentTimeMillis();
    try {
        c1.get(500, TimeUnit.MILLISECONDS);
    } catch(TimeoutException e) {
        c1.cancel(true);
        System.out.println("timeout after "
                + (System.currentTimeMillis() - start) + "ms, canceled");
    }
    c2.get();
}

Note that the interruption may still be too slow to prevent the printing of 1. On my machine, I needed at least Thread.sleep(1100); to interrupt the task before the printing.

Upvotes: 1

Michael
Michael

Reputation: 44150

Why do you think it should? What if 2 consumers want to handle the result of the same future, and one is prepared to wait longer than the other? The first would kill it for the second, which it might've completed in time for.

In any case, Java doesn't have the concept of killing a task. The best you can do is set a flag asking it to stop - an interrupt - but that relies on the task checking and respecting the flag. Your tasks do, since Thread.sleep() implicitly checks the interrupt flag, but it's a contrived example, and a lot of tasks wouldn't ever check it.

Upvotes: 2

Related Questions