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


Reply via email to