Ok thanks for the confirmations! If I'm understanding correctly, in the future the way to guarantee once-only initialization of a ClassValue would be to have it produce a StableValue, of which only one will be installed, and which will then be guaranteed by the JVM to have once-only initialization and a foldable, stable result. Until that is generally available, I should use a factory object with double-checked locking to ensure once-only initialization (which is essentially what my own ClassValue did, but built as a wrapper around java.lang.ClassValue).
On Wed, May 21, 2025 at 1:59 PM Remi Forax <[email protected]> wrote: > > > ------------------------------ > > *From: *"Chen Liang" <[email protected]> > *To: *"Da Vinci Machine Project" <[email protected]>, "Charles Oliver > Nutter" <[email protected]> > *Sent: *Wednesday, May 21, 2025 8:47:24 PM > *Subject: *Re: Years later, finally integrating ClassValue... but... > > Hi Charles, > Indeed, the docs for "computeValue" was wrong. The doc for "get" was > right. The upcoming JDK 25 will provide a spec update that simplifies the > mental model of a ClassValue and correct these mistakes in the docs. > > ClassValue always have been utilizing racy computation, that multiple > threads can compute a value, so there is minimal locking - the only locking > would be at the time when a thread tries to associate a value. This > association is essentially a CAS, where only one thread's computeValue > result is ever installed. There is no synchronization lock on computeValue, > and there is no way for a synchronized computeValue to check whether a > value is already associated. The only threading guarantee provided by > ClassValue is that the associated value's identity/value is unique and its > computation result is published to all threads that saw the value via a get. > > If you wish for synchronous initialization, I think you can store a > container, such as a memoized function (such as StableValue::supplier), in > a ClassValue, and then call the function (or a synchronized initializer) on > all accessing threads - this ensures the is exactly one memoized function > object, and that object is initialized exactly once. > > > yes, a double check locking (or a stable value but it's in preview in 25) > solve the issue. > > > Regards, > Chen Liang > > > Rémi > > ------------------------------ > *From:* mlvm-dev <[email protected]> on behalf of Charles Oliver > Nutter <[email protected]> > *Sent:* Wednesday, May 21, 2025 12:42 PM > *To:* Da Vinci Machine Project <[email protected]> > *Subject:* Years later, finally integrating ClassValue... but... > > Due to issues for years trying to understand the lifecycle of class/value > pairs stored in ClassValue, I am only now integrating the JDK-provided > version of ClassValue into JRuby's logic to store method tables for Java > classes. And I have run into a new inconsistency I want to clarify. > > The docs for ClassValue state this for the "computeValue" method: > > "This method will be invoked within the first thread that accesses the > value with the get method. > Normally, this method is invoked at most once per class, but it may be > invoked again if there has been a call to remove." > > To me, that means computeValue will be invoked *exactly once* per class > (ignoring removals), regardless of how many threads attempt to compute it > at the same time. Otherwise, what's the point of saying it will be invoked > by the "first thread"? > > But then the docs for "get" say something different: > > "Returns the value for the given class. If no value has yet been computed, > it is obtained by an invocation of the computeValue method. > The actual installation of the value on the class is performed atomically. > At that point, if several racing threads have computed values, one is > chosen, and returned to all the racing threads." > > Ok hold up... so now it's possible for multiple threads to independently > computeValue? These two statements don't appear to mesh... I'm looking for > the behavior computeValue describes: basically computeIfAbsent. But in > practice (and from what I have read of the current implementation), > multiple threads might call computeValue for a given class. > > In JRuby, where the computation of this value also sets up global > namespace tables, it results in warnings that the namespace entry has been > initialized multiple times. > > Adding synchronized to my implementation of computeValue does not help; it > just means two threads don't compute at the same time. They will block > until the first thread finishes its computeValue, and while that first > thread is initializing the ClassValue, they'll proceed to computeValue > several more times. > > This was not a problem with my home-grown ClassValue implementation > because I double-check the cache before proceeding into synchronized code > that makes the computeValue call (computeIfAbsent behavior). > > I can work around this by also overriding ClassValue.get to be > synchronized, but the existing behavior does not seem right to me. It works > this way on 1.8, 21, and 24, so nothing has changed. Either the docs are > wrong or the implementation is wrong. > > *Charles Oliver Nutter* > *Architect and Technologist* > Headius Enterprises > https://www.headius.com > [email protected] > > _______________________________________________ > mlvm-dev mailing list > [email protected] > https://mail.openjdk.org/mailman/listinfo/mlvm-dev > >
_______________________________________________ mlvm-dev mailing list [email protected] https://mail.openjdk.org/mailman/listinfo/mlvm-dev
