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


Reply via email to