RFR: 8197812: (ref) Data race in Finalizer
Peter Levart
peter.levart at gmail.com
Wed Feb 14 08:47:38 UTC 2018
Hi Again,
While studying the code of Finalizer I found a strange discrepancy
between handling of 'unfinalized' field during processing of
Finalizer(s) taken from ReferenceQueue and taken from the 'unfinalized'
pointer itself (as in runAllFinalizers()). The remove() method is as
follows:
private void remove() {
synchronized (lock) {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
this.next = this; /* Indicates that this has been
finalized */
this.prev = this;
}
}
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. For comparison, when Finalizer(s) are
processed from the 'unfinalized' pointer directly (as in
runAllFinalizers()), the following is executed:
for (;;) {
Finalizer f;
synchronized (lock) {
f = unfinalized;
if (f == null) break;
unfinalized = f.next;
}
f.runFinalizer(jla);
}
... so it is assumed that the 'unfinalized' always points to the head.
If this was not true, not all Finalizer(s) would be processed.
So what I'm asking is whether this simplified if statement in remove()
would be equivalent:
if (unfinalized == this) {
unfinalized = this.next;
}
Or am I missing some situation?
Regards, Peter
On 02/14/2018 09:19 AM, Peter Levart wrote:
> Hi Martin,
>
> On 02/14/2018 07:58 AM, Peter Levart wrote:
>> It may be that the intent was to refrain from using the shared 'lock'
>> lock for the 2nd and subsequent calls to runFinalizer() and only use
>> the more fine-grained 'this' lock in this case?
>>
>> If someone was able to call runFinalizer() on the same instance in a
>> loop he could prevent or slow-down normal processing of other
>> Finalizer(s) if the shared 'lock' was always employed. What do you
>> think?
>
> I checked all uses of runFinalizer() and I don't think it can be
> called more than twice for the same Finalizer instance (for example if
> there is a race between runAllFinalizers() and processing of
> Finalizers taken from the ReferenceQueue). So your patch is a nice
> simplification.
>
> Regards, Peter
>
More information about the core-libs-dev
mailing list