Years later, finally integrating ClassValue... but...
Remi Forax
forax at univ-mlv.fr
Wed May 21 18:59:09 UTC 2025
> From: "Chen Liang" <chen.l.liang at oracle.com>
> To: "Da Vinci Machine Project" <mlvm-dev at openjdk.org>, "Charles Oliver Nutter"
> <headius at headius.com>
> 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 <mlvm-dev-retn at openjdk.org> on behalf of Charles Oliver Nutter
> <headius at headius.com>
> Sent: Wednesday, May 21, 2025 12:42 PM
> To: Da Vinci Machine Project <mlvm-dev at openjdk.java.net>
> 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/ | https://www.headius.com ]
> [ mailto:headius at headius.com | headius at headius.com ]
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.org
> https://mail.openjdk.org/mailman/listinfo/mlvm-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/mlvm-dev/attachments/20250521/6c070c01/attachment-0001.htm>
More information about the mlvm-dev
mailing list