RFR: 8197812: (ref) Data race in Finalizer

Peter Levart peter.levart at gmail.com
Wed Feb 14 09:39:09 UTC 2018


I could even claim that simplifying the if statement in remove() to:

     if (unfinalized == this) {
         unfinalized = this.next;
     }

makes checking for hasBeenFinalized() in runFinalizer() redundant as it 
would not be possible for runFinalizer() to be called more than once for 
each Finalizer instance because:
- ReferenceQueue never returns the same Reference instance twice or more 
times.
- 'unfinalized' will never point back to the same Finalizer instance for 
the 2nd time, because it always "travels" in the forward direction 
(unfinalized = unfinalized.next).

Regards, Peter

(I rest now).

On 02/14/2018 10:24 AM, Peter Levart wrote:
> 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