RFR: 8197812: (ref) Data race in Finalizer
Peter Levart
peter.levart at gmail.com
Wed Feb 14 09:24:35 UTC 2018
Hi,
I may have found a situation described here...
On 02/14/2018 09:47 AM, Peter Levart wrote:
>
> When Finalizer(s) are taken from ReferenceQueue, they are processed in
> arbitrary order, so once in a while it can happen that the Finalizer
> at the head of the list (pointed to by 'unfinalized') is processed
> this way. The body of the 1st if statement in above method is executed
> in such situation:
>
> if (unfinalized == this) {
> if (this.next != null) {
> unfinalized = this.next;
> } else {
> unfinalized = this.prev;
> }
> }
>
> But I can't figure out a situation when this.next would be null while
> this.prev would be non-null. In other words, when 'unfinalized' would
> not point to the head of the list.
This is the situation:
unfinalized
|
v
Finalizer[1].next --> Finalizer[2].next --> null
Finalizer[2].prev --> Finalizer[1].prev --> null
runAllFinalizers() is executed and the 1st pass of the for loop takes
the Finalizer[1] from the 'unfinalized' pointer and moves the pointer to
Finalizer[2]:
unfinalized ----------------
|
v
Finalizer[1].next --> Finalizer[2].next --> null
Finalizer[2].prev --> Finalizer[1].prev --> null
...now finalizer thread kicks-in and takes the Finalizer[2] from the
ReferenceQueue and calls remove(). The 1st if statement in remove()
would evaluate the condition to true, as 'unfinalized' now points to
Finalizer[2], so the body of 1st if would make unfinalized point back to
Finalizer[1]:
unfinalized
|
v
Finalizer[1].next --> Finalizer[2].next --> null
Finalizer[2].prev --> Finalizer[1].prev --> null
...and the remaining remove() code would unlink the Finalizer[2] from
the list, resulting in this state of the list:
unfinalized
|
v
Finalizer[1].next --> null
Finalizer[1].prev --> null
This might be seen as a "correct" state, because Finalizer[1] has not
been removed from the list yet although it is in the process of being
removed because the next thing runAllFinalizers() loop would do is it
would call runFinalizer() on it. So the simplified handling in remove():
if (unfinalized == this) {
unfinalized = this.next;
}
...would result in correct behavior too. Even more so because if the 2nd
call to runAllFinalizers() was being executed concurrently (I don't know
if this is possible though), the Finalizer[1] from above situation would
be taken by the concurrent call again and its runFinalizer() executed.
runFinalizer() is idempotent so no harm done here, but...
Regards, Peter
More information about the core-libs-dev
mailing list