[9] RFR(S): 8005873: JRuby test_respond_to.rb asserts with: MT-unsafe modification of inline cache

Vitaly Davidovich vitalyd at gmail.com
Mon May 19 21:49:17 UTC 2014


Synchronized is only on the setter, which is the slow path.

Sent from my phone
On May 19, 2014 5:43 PM, "Remi Forax" <forax at univ-mlv.fr> wrote:

> Hi John,
> i don't understand how the Jit can remove the memory barrier due to the
> synchronized when prev is not null ?
>
> Rémi
>
> Envoyé avec AquaMail pour Android
> http://www.aqua-mail.com
>
>
> Le 19 mai 2014 21:52:20 John Rose <john.r.rose at oracle.com> a écrit :
>
>  On May 15, 2014, at 2:04 AM, Vladimir Ivanov <
>> vladimir.x.ivanov at oracle.com> wrote:
>>
>> > Cache is written only once, so it has only 2 states (null and non-null
>> value) during it's lifecycle.
>> > The only stale value cachedLambdaForm() can see is null, but then the
>> caller tries to initialize the cache and after acquiring the lock in
>> setCachedLambdaForm() sees actual value.
>> > So, the worst thing can happen if readers aren't synchronized is
>> creation of unnecessary LF (which go dead right away) in rare cases.
>>
>>
>> Lazy evaluation is a pattern that we use (or try to use) in a lot of
>> places.  It's important to understand it and get it right.
>>
>> With JDK-8005873, the root cause is that lazy evaluation is producing
>> multiple right answers, and an assert is throwing a panic.  The correct fix
>> is to use a CAS or lock (or equivalent) to make sure that, even if several
>> threads race to produce a right answer, only one right answer gets
>> published and used.
>>
>> I coded this completely free of locks, in 'setCachedLambdaForm', because
>> I thought that multiple right answers would be harmless.  That's arguably
>> still correct, but if it makes assertions fire, we should clean up the edge
>> case, rather than detune the assert.
>>
>> Uniquification can be done with a lock or a CAS.  CAS is preferable, but
>> hard to code.  AtomicReferenceArray provides easy access to CAS, but you
>> pay a cost for simple 'get', since that is volatile.  That's not desirable;
>> in fact the @Stable property is more appropriate.  On balance I would
>> prefer a simple lock, such as this:
>>
>> +    synchronized
>>      public LambdaForm setCachedLambdaForm(int which, LambdaForm form) {
>> -        // Should we perform some sort of CAS, to avoid racy duplication?
>> +        // Simulate a CAS, to avoid racy duplication of results.
>> +        LambdaForm prev = lambdaForms[which];
>> +        if (prev != null)  return prev;
>>          return lambdaForms[which] = form;
>>      }
>>
>> Once lazy results are guaranteed unique the rest of the algorithm is
>> fine, I think.  See below.
>>
>> — John
>>
>> P.S.  More discussion of lazy evaluation and races, assuming published
>> values are unique.
>>
>> If the state variable behind a lazy evaluation is a pointer to
>> safely-published data, then a racy read will pick up either the initial
>> state (null) or the desired data.
>>
>> A slow path for null detection needs to reliably pick up the true value
>> of the variable, to avoid starvation and multiple initialization.  Doing
>> the slow-path read and write inside of a lock satisfies the requirements of
>> the JMM for correct race-free code.
>>
>> Outside of the lock, it is important that anything observed by the racy
>> read eventually lead to a correct answer, even if the racy read gives an
>> out-of-date answer.
>>
>> If the lock's critical section is trivial (no object construction or side
>> effects) then a CAS is usually faster, but it is hard to code correctly.
>>  The new JMM will fix this we hope and make it possible to perform safe and
>> sane CAS operations.  Meanwhile, the atomic reference array would work, but
>> it is (IMO) overly expensive since it performs
>>
>> The bottom line is that we don't need any volatile references on the fast
>> path.
>>
>> One hazard is that the JMM does not guarantee safe publication in either
>> of the following two cases:
>>
>> Caveat #1. Data is accessed along a path that does not use a final field.
>> Caveat #2. Data is accessed along a path that has been modified since
>> construction.
>>
>> Caveat #1 is usually (by itself) not a problem, if the access path goes
>> through an object with at least one final field.  (That's because existing
>> implementations use a broad fence at the end of a constructor if an object
>> contains any non-static finals.)  And the new JMM may further downgrade or
>> remove Caveat #1.
>>
>> Caveat #2 includes the case of a non-final field in a "mostly final"
>> object.  The field LambdaForm.vmentry is such a field.  Any non-final
>> fields accessed through a vmentry patched into a published LambdaForm are
>> racy, unless some sort of fence has been issued before the vmentry object
>> is patched into the lambda form.
>>
>> Caveat #2 It also includes the case where the path goes through an array
>> element which was changed since the root object was constructed.  This
>> happens in various tabular structures where an array that starts out full
>> of nulls ends up containing lazily evaluated entries.  The entries should
>> each be safely published.
>>
>> Caveat #2 does *not* apply to stable array substructures like
>> LamdaForm.names, if (as is the case with 'names') the arrays are assigned
>> before the enclosing object is fully constructed, and the array references
>> themselves are final.
>>
>>
>>


More information about the hotspot-dev mailing list