-
Bug
-
Resolution: Cannot Reproduce
-
P4
-
None
-
5.0
-
generic
-
generic
the jdk7 implementation of FutureTask has small correctness issues
with state transition. If we look at innerRun
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
//// Suppose cancel is invoked and returns here.
//// At this point, isDone() returns true.
runner = Thread.currentThread();
//// At this point, isDone() returns false.
if (getState() == RUNNING) { // recheck after setting thread
we have a case where isDone() could return false after cancel() returns,
even though isCancelled() returns true.
It's the old problem of having state transitions recorded in two
separate fields, when we can only update one of them atomically.
We can make the races rarer, but I'm not sure we can eliminate them
without moving all significant state into the one monotonically
incremented CASable int.
-----
There's the other problem of interrupt delivery.
It is not absolutely guaranteed that cancel(true) will indeed interrupt
the task instead of its surrounding code.
-----
I believe both of the above problems by designing
state transitions very carefully, without any additional overhead for
the "normal" case of successful run().
Perhaps there's a general principle here that any class which can
be designed to have an ordered set of states with monotonically
increasing transitions doesn't need locks, only CAS.
I think we can solve the incorrectly delivered interrupt problem
by (as usual) introducing a new state INTERRUPTING, with cancel()
transitioning RUNNING -> INTERRUPTING -> CANCELLED.
If the runner thread notices that state == INTERRUPTING, it must
wait (via acquireShared) for the state to first advance to CANCELLED,
before returning from run().
If we keep track of whether an interrupt has been
delivered, we can guarantee that it was delivered in a specific window,
which would allow us to reconsider restoring interrupt state.
In addition, I believe this can be done while supporting set() called
independently, that is, not directly from run(), without any extra
CAS state transitions in the normal case. See:
6663476: FutureTask.get() may return null if set() is not called from run()
with state transition. If we look at innerRun
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
//// Suppose cancel is invoked and returns here.
//// At this point, isDone() returns true.
runner = Thread.currentThread();
//// At this point, isDone() returns false.
if (getState() == RUNNING) { // recheck after setting thread
we have a case where isDone() could return false after cancel() returns,
even though isCancelled() returns true.
It's the old problem of having state transitions recorded in two
separate fields, when we can only update one of them atomically.
We can make the races rarer, but I'm not sure we can eliminate them
without moving all significant state into the one monotonically
incremented CASable int.
-----
There's the other problem of interrupt delivery.
It is not absolutely guaranteed that cancel(true) will indeed interrupt
the task instead of its surrounding code.
-----
I believe both of the above problems by designing
state transitions very carefully, without any additional overhead for
the "normal" case of successful run().
Perhaps there's a general principle here that any class which can
be designed to have an ordered set of states with monotonically
increasing transitions doesn't need locks, only CAS.
I think we can solve the incorrectly delivered interrupt problem
by (as usual) introducing a new state INTERRUPTING, with cancel()
transitioning RUNNING -> INTERRUPTING -> CANCELLED.
If the runner thread notices that state == INTERRUPTING, it must
wait (via acquireShared) for the state to first advance to CANCELLED,
before returning from run().
If we keep track of whether an interrupt has been
delivered, we can guarantee that it was delivered in a specific window,
which would allow us to reconsider restoring interrupt state.
In addition, I believe this can be done while supporting set() called
independently, that is, not directly from run(), without any extra
CAS state transitions in the normal case. See:
6663476: FutureTask.get() may return null if set() is not called from run()
- relates to
-
JDK-6663476 FutureTask.get() may return null if set() is not called from run()
-
- Open
-