Reputation: 405
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.
future.isCancelled()
. The result is false
, because the task is still in progress.future.cancel()
.future.isDone()
. The result is true
now.true
, and it's incorrect answer.How to avoid this issue?
Upvotes: 11
Views: 8386
Reputation: 12131
I believe that the given solution is safe and that the worry for concurrency bugs is unfounded.
These are my three arguments:
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.
None of the most popular implementations, FutureTask
, CompletableFuture
and Guava's AbstractFuture
have the risk this re-ordering.
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.
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
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