RFR: 8351996: Behavioral updates for ClassValue::remove [v10]

Viktor Klang vklang at openjdk.org
Wed Apr 30 16:32:50 UTC 2025


On Tue, 29 Apr 2025 19:03:08 GMT, Chen Liang <liach at openjdk.org> wrote:

>> The recent patch #23866 makes calling `ClassValue::remove()` from `ClassValue::computeValue()` end up in infinite loops while fixing the stale value risk from the method.
>> 
>> The proposed fix is to preserve the stale value risk fix, and update the remove-from-compute behavior from the original designated no-op behavior to throwing an exception, as the original behavior conflicts with the stale value fix.
>> 
>> The implementation track the owner thread in promises (accessed in locked section); as a result, we can fail-fast on recursive removals from `computeValue`. I did not choose to use `ThreadTracker` as it is designed for single tracker and multiple threads, while this case here sees often just one thread, and the threads outlive the promise objects.
>> 
>> Also updated the API specs for `remove` to more concisely describe the memory effects. Please review the associated CSR as well.
>
> Chen Liang has updated the pull request with a new target base due to a merge or a rebase. The incremental webrev excludes the unrelated changes brought in by the merge/rebase. The pull request contains 17 additional commits since the last revision:
> 
>  - Rewrite impl to follow the new simplified spec
>  - Merge branch 'master' of https://github.com/openjdk/jdk into fix/classvalue-compute-remove
>  - Try to simplify the model - use the finish of computeValue
>    
>  - Test updates - remove repetition, test case for no stale installation
>  - Fix incorrect promise removal when unnecessary and add regression test
>  - Cannot test for recursion eagerly - add test case
>  - More spec, eager exception, finish with existing, thanks John
>  - Merge branch 'master' of https://github.com/openjdk/jdk into fix/classvalue-compute-remove
>  - docs
>  - Remove the throwing behavior due to shallow reentrancy
>  - ... and 7 more: https://git.openjdk.org/jdk/compare/56c8a5ac...221d51e7

Hi @liach, I had a look, primarily on the changes rather than the tests, for this pass.

src/java.base/share/classes/java/lang/ClassValue.java line 95:

> 93:      * <ul>
> 94:      * <li>This association is already associated by another attempt. The
> 95:      * associated value is returned.</li>

It's a bit unclear which association is referred to in the second sentence, I think that could be made clearer with something like:

Suggestion:

     * <li>This association is already associated by another attempt, so that value is returned.</li>

src/java.base/share/classes/java/lang/ClassValue.java line 124:

> 122:      * {@return the value associated to the given {@code Class}}
> 123:      * <p>
> 124:      * This method first performs a read-only access, and returns the associated

I'm not 100% sure on the "read-only" label here, as it does recompute-and-store if there is no current value.

src/java.base/share/classes/java/lang/ClassValue.java line 167:

> 165:     public void remove(Class<?> type) {
> 166:         ClassValueMap map = getMap(type);
> 167:         map.removeAccess(this);

Should this really be "Access" and not "Association"?

src/java.base/share/classes/java/lang/ClassValue.java line 318:

> 316:     Version<T> version() { return version; }
> 317:     void bumpVersion() { version = new Version<>(this); }
> 318:     static final class Version<T> {

Since this is all final components, it might serve better as a record?

src/java.base/share/classes/java/lang/ClassValue.java line 377:

> 375:                 return new Entry<>(v2, value);
> 376:             }
> 377:             return this;

The following reads slightly better (to me):

Suggestion:

            return version.refersTo(v2) ? this : new Entry<>(v2, value);

test/jdk/java/lang/invoke/ClassValueTest.java line 179:

> 177: 
> 178:     private static final long COMPUTE_TIME_MILLIS = 100;
> 179:     private static final Duration TIMEOUT = Duration.of(2, ChronoUnit.SECONDS);

In order to reduce spurious failures here, I'd probably put this timeout a bit longer—let's say 5s.

-------------

PR Review: https://git.openjdk.org/jdk/pull/24043#pullrequestreview-2806354549
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2068235676
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2069033640
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2069037387
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2069047258
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2069052518
PR Review Comment: https://git.openjdk.org/jdk/pull/24043#discussion_r2069056808


More information about the core-libs-dev mailing list