const.grigoryev
const.grigoryev

Reputation: 405

How to test if Future is complete without race?

I'm tracking execution of a task with standard Future object. There are the following valid states of that task (as I see in Guava code of AbstractFuture class):

Future.isDone() returns true if and only if the state is completed, cancelled or interrupted. Future.isCancelled() returns true if and only if the state is either interrupted or cancelled.

Ok, but I need to check if the task is completed. There is an obvious way to do this:

boolean isCompleted = future.isDone() && !future.isCancelled();

Unfortunatelly, a nasty concurrency bug hides there.

  1. Thread #1 invokes future.isCancelled(). The result is false, because the task is still in progress.
  2. Thread #2 cancels the task calling future.cancel().
  3. Thread #1 invokes future.isDone(). The result is true now.
  4. Evaluation of expression above yields true, and it's incorrect answer.

How to avoid this issue?

Upvotes: 11

Views: 8386

Answers (2)

Lii
Lii

Reputation: 12131

I believe that the given solution is safe and that the worry for concurrency bugs is unfounded.

These are my three arguments:

  1. Although not explicitly stated in the documentation Future I believe that it contrary to the spirit of the design of the class to allow such re-orderings as the one described in the question text.

  2. None of the most popular implementations, FutureTask, CompletableFuture and Guava's AbstractFuture have the risk this re-ordering.

  3. The Javadoc of Future states the following:

    Memory consistency effects: Actions taken by the asynchronous computation happen-before actions following the corresponding Future.get() in another thread.

    I believe that it is resonable to assume that this applies not only the actions taken be by the asynchronous computation, but also to actions such as state transitions. This happens-before relationship forbids the problematic re-ordering

Conclusion: I believe that it is a documentation bug in Future that this is not explicitly stated.

Reason why popular implementation do not risk this re-ordering

The given solution is this:

boolean isCompleted = future.isDone() && !future.isCancelled();

In both FutureTask, CompletableFuture and Guava's AbstractFuture these operations involve reading an internal volatile state field. This state field is also written in every state transition. This forbids re-ordering of those operations.

The Java Memory Model FAQ states this:

  • Each action in a thread happens before every action in that thread that comes later in the program's order.
  • A write to a volatile field happens before every subsequent read of that same volatile.

This implies that if isDone sees the value of the state Completed then there is a happens-before relationship between the write action of the state and the read action in isDone and isCancelled, by the second cited bullet in JMM FAQ.

There is also a happens-before relationship between the call to isDone and the call to isCancelled, by the first cited bullet in JMM FAQ.

Hence the re-ordering between step 1. and step 3. in the question text is not possible.

The question poster worries that values are "pre-evaluated speculativelly with result caching". That kind of things can only be observable in the context of writes and reads from concurrent threads that are not ordered by volatile, synchronized or other mechanisms.

Upvotes: 0

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280181

You can try calling get(..) with an extremely short timeout. If it returns a value, it was completed. If you get a TimeoutException, it wasn't. If you get any other of the possible exceptions, it was either cancelled, failed, or was interrupted.

Upvotes: 4

Related Questions