RFR: 8132306: java/lang/ref/ReferenceEnqueue.java fails with "RuntimeException: Error: poll() returned null; expected ref object"

Peter Levart peter.levart at gmail.com
Thu Jul 30 10:41:35 UTC 2015

On 07/30/2015 12:24 PM, David Holmes wrote:
> On 30/07/2015 8:20 PM, Peter Levart wrote:
>> On 07/30/2015 11:12 AM, Daniel Fuchs wrote:
>>> Hi Peter,
>>> I'm glad you're looking at this too! We can't have too many eyes :-)
>>> On 30/07/15 10:38, Peter Levart wrote:
>>>> Suppose we have a Reference 'r' and it's associated ReferenceQueue 
>>>> 'q'.
>>>> Currently it can happen that the following evaluates to true, which is
>>>> surprising:
>>>> q.poll() == null && r.isEnqueued()
>>> But on the other hand this can only happen if two different
>>> threads are polling the queue - in which case only one of them
>>> will get the reference. In such a situation, the symmetric condition
>>> would not be surprising (as the other thread would
>>> get q.poll() != null):
>>>    r.isEnqueued() && q.poll() == null
>>> The original bug fixed by Kim is more surprising, because there's only
>>> one Thread doing the polling, and it does get:
>>>    r.isEnqueud() && q.poll() == null
>>> although nobody else is polling the queue.
>>> This should no longer occur in this situation with Kim's fix.
>>> cheers,
>>> -- daniel
>> Yes, these are two different issues. The one Kim has fixed is the race
>> of above expressions with Queue.enqueue() (when the queue is changing
>> from the associated instance to ENQUEUED). The one I'm pointing at and
>> Kim has already identified as potential issue is the race of the
>> following expression:
>> r.isEnqueued() && q.poll() == null && r.isEnqueued() ==> true
>> ....with Queue.[realy]poll() (when the queue is changing from ENQUEUED
>> to NULL).
>> Which only happens if at least two threads are polling the queue, but is
>> equally surprising and prone to cause bugs, don't you think?
> Sorry I'm missing your point. The expression:
> r.isEnqueued() && q.poll() == null
> is exactly the race the current fix is addressing. Adding a second 
> check of r.isEnqueued() which still returns true does not add anything 
> that I can see. ??
> David

The expressions are similar, but the race is different. The one 
addressed by Kim is the race of:

r.isEnqueued() && q.poll() == null ==> true

with q.enqueue(r)

There, the reversal of assignments to q.head and r.queue in 
ReferenceQueue.enqueue() fixes the issue.

The one I'm pointing at is the race of:

r.isEnqueued() && q.poll() == null && r.isEnqueued() ==> true

with q.poll()

Here, the fix would be to also revert the assignments to q.head and 
r.queue in ReferenceQueue.reallyPoll() this time.

The 1st race is the race with enqueue-ing and the 2nd race is the race 
with de-queueing. Initially, my "surprising" expression was:

q.poll() == null && r.isEnqueued() ==> true

...but this is not representative, as it is also an expected outcome 
when racing with enqueue-ing. So I added a pre-condition to express the 
fact that it happens when racing with de-queueing only:

r.isEnqueued() && q.poll() == null && r.isEnqueued() ==> true

What is surprising in the above expression evaluating to true is the 
fact that 'r' appears to be enqueued before and after the q.poll() 
returns null. I can easily imagine code that would fail because it never 
imagined above expression to evaluate to true. For example:

if (r.isEnqueued()) {
     Reference s = q.poll();
     if (s == null) {
         // somebody else already dequeued 'r'
         assert !r.isEnqueued();

Regards, Peter

>> Regards, Peter

More information about the core-libs-dev mailing list