On 06/19/2013 09:17 AM, Thomas Schatzl wrote:
Hi,

On Wed, 2013-06-19 at 12:47 +1000, David Holmes wrote:
Hi Thomas,

On 19/06/2013 7:08 AM, Thomas Schatzl wrote:
Hi all,

    can I have reviews for the following change?

It happens if multiple threads are enqueuing and dequeuing reference
objects into a reference queue, that Reference objects may be enqueued
at multiple times.

This is because when java.lang.ref.ReferenceQueue.poll() returns and
inactivates a Reference object, another thread may just be during
enqueuing it again.
Are we talking about different queues here? Otherwise both poll() and
enqueue() are using synchronized(lock). I also note that enqueue
synchronizes on the Reference itself, which suggests that poll (actually
reallyPoll) should also be synchronizing on the reference (though we
have a nested lock inversion problem that would need to be solved).
This does not help imo. Still there may be temporary storage containing the 
original ReferenceQueue reference, and .enqueue() gets called on it with the 
now inactive Reference.

Enqueue() and (really-)poll() themselves already synchronize on the
"lock" lock to guard modification of the queue, this is safe.

The synchronize(r) statement in ReferenceQueue.enqueue() is about 
synchronization with Reference.isEnqueued() I think. Actually I am not sure 
whether the synchronization between isEnqueued() and enqueue() makes a 
difference.

Hi Thomas,

It doesn't make a difference between Reference.isEnqueued() and ReferenceQueue.poll(), since there isn't any synchronization between the two. So isEnqueued() can still be returning true while another thread has already de-queued the instance. I guess the real use of isEnqueued() method is reliable detection of false -> true transition only.

I can't see why the isEnqueued() method checks for both (next != null && queue == ENQUEUED). Wouldn't the check for (queue == ENQUEUED) be enough? In that case the Reference.queue field could be made volatile and synchronization on Reference instance removed.

While you're at it, the reallyPoll() could optimize the double volatile read from head and only perform it once:

private Reference<? extends T> reallyPoll() { /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            head = (r.next == r) ? null : r.next;
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }



Another solution would be to synchronize enqueue() and poll() on the queue 
itself, and check whether the reference passed to enqueue() is inactive or not 
(i.e. this check is still needed).

ReferenceQueue.this and ReferenceQueue.lock are 1<->1. What difference would that make?

Regards, Peter


ReferenceHandlerThread.run():

0: [...]
1: ReferenceQueue q = r.queue; // r is the reference
2: if (r != ReferenceQueue.NULL)
3:   q.enqueue().
Between lines 2 and 3, q contains a reference to the old ReferenceQueue (which 
is not ReferenceQueue.NULL). So if the thread is switched there, i.e. the main 
thread does a poll() on q, the main thread makes r inactive. When switching 
back to the reference handler thread (or any other thread), enqueue() of that 
Reference r will still be called on the original q. The actual r.queue is 
already ReferenceQueue.NULL from the poll(). (i.e. the Reference is inactive). 
This change guards against that situation as this is (imo) an unexpected 
behavior (that you can enqueue a Reference multiple times; and an already 
inactive one too).

The only solution would be synchronizing on r in the ReferenceHandler code to 
avoid that (I think). However then everyone that uses Reference.enqueue() 
(which uses ReferenceQueue.enqueue()) would need to synchronize on the 
Reference to make the code thread safe. I haven't seen that in the 
documentation.

As mentioned this situation may occur independently of whether the 
ReferenceHandler thread is involved or not.

I.e. if you look at Reference.enqueue() which reads as

   this.queue.enqueue(this)

This is the same situation, if you consider that "this.queue" may be 
temporarily stored in e.g. a register during the thread switch.


Webrev with test case
http://cr.openjdk.java.net/~tschatzl/8014890/webrev/

JIRA:
https://jbs.oracle.com/bugs/browse/JDK-8014890

bugs.sun.com
http://bugs.sun.com/view_bug.do?bug_id=8014890

Testing:
jck, jprt, manual testing

Note that I also need a sponsor to push in case this change is approved.
Thanks,
   Thomas



Reply via email to