Andronicus
Andronicus

Reputation: 26046

Timeout with CompletableFuture and CountDownLatch

I want to wrap a Runnable in CompletableFuture to be computed asynchronously, but with control over when does the computation begin and end. I've created a CompletableFuture with CountDownLatch to block the processing, but the following snippet throws an error:

CountDownLatch countDownLatch = new CountDownLatch(1);
CompletableFuture completableFuture = CompletableFuture.runAsync(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Stop");
});
Thread.sleep(1000L);
System.out.println("Start");
completableFuture.get(1000L, TimeUnit.MILLISECONDS);
countDownLatch.countDown();

Start Exception in thread "main" java.util.concurrent.TimeoutException at java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1771) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915) at Sandbox.main(Sandbox.java:23)

When I call get without timeout on the other hand, it freezes (only Start is printed).

I expect the runnable in CompletableFuture to run when countDownLatch.countDown(); is called.

Upvotes: 3

Views: 3598

Answers (2)

Ravindra Ranwala
Ravindra Ranwala

Reputation: 21124

You are waiting till the timeout expires without allowing the thread to proceed. The Future.get is blocking and that will never allow you to countDown the Latch before the timeout expires ever, hence your thread never completes. What you have to do here is, first, let the thread proceed by calling the countDown on the Latch and then wait with a timeout in the get call. Just inverting the two lines would solve the issue. Here's how it looks.

countDownLatch.countDown();
completableFuture.get(1000L, TimeUnit.MILLISECONDS);

In fact, if you remove the timeout from the get call (it blocks indefinitely), then this is a typical example of a Deadlock in a system. The worker thread waits until the main thread counts down the latch, while main thread waits for the worker thread to complete so that it can go ahead and countDown the latch. Fortunately, the time out passed to get enables Probabilistic deadlock avoidance. On the contrary, you can cancel the future at any time and avoid potential deadlocks as far as your tasks are responsive to the interruption.

Upvotes: 5

Amit Bera
Amit Bera

Reputation: 7315

Because of CompletableFuture#get is a blocking call. So, countDownLatch.countDown(); will not execute till the time CompletableFuture#get get the result. CompletableFuture will not complete and return the result as it will wait to countDownLatch to count down. So, basically you have created a dependency between 2 thread such that one will wait for another and vice-versa.

Upvotes: 4

Related Questions