Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8310913 Move ReferencedKeyMap to jdk.internal so it may be shared #14684

Closed
wants to merge 17 commits into from

Conversation

JimLaskey
Copy link
Member

@JimLaskey JimLaskey commented Jun 27, 2023

java.lang.runtime.ReferencedKeyMap was introduced to provide a concurrent caching scheme for Carrier objects. The technique used is generally useful for a variety of caching schemes and is being moved to be shared in other parts of the jdk. The MethodType interning case is one example.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8310913: Move ReferencedKeyMap to jdk.internal so it may be shared (Enhancement - P3)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/14684/head:pull/14684
$ git checkout pull/14684

Update a local copy of the PR:
$ git checkout pull/14684
$ git pull https://git.openjdk.org/jdk.git pull/14684/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 14684

View PR using the GUI difftool:
$ git pr show -t 14684

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/14684.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jun 27, 2023

👋 Welcome back jlaskey! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jun 27, 2023

@JimLaskey The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Jun 27, 2023
@JimLaskey JimLaskey marked this pull request as ready for review June 29, 2023 15:18
@openjdk openjdk bot added the rfr Pull request is ready for review label Jun 29, 2023
@mlbridge
Copy link

mlbridge bot commented Jun 29, 2023

Copy link
Member

@naotoj naotoj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me. Good to see this class is being shared, as this can be used for refactoring the locale-related caches (#14404).

Comment on lines 82 to 84
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
* Do not rely on its availability.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this is shared by other components, do we still need this warning?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True that.

@openjdk
Copy link

openjdk bot commented Jun 29, 2023

@JimLaskey This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8310913: Move ReferencedKeyMap to jdk.internal so it may be shared

Reviewed-by: naoto, rriggs, mchung, liach

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jun 29, 2023
Copy link
Contributor

@RogerRiggs RogerRiggs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MethodType's ConcurrentWeakInternSet uses NativeReferenceQueue which uses native monitors (that was added for virtual threads). This change uses the reference queue using j.u.c locks. Has you confirmed that this is not an issue? Or ReferencedKeySet can take a parameter to specify using the native reference queue?

In ReferencedKey::equals method, can use Reference::refersTo instead of Objects.equals.

@JimLaskey
Copy link
Member Author

@mlchung I missed the use of newNativeReferenceQueue. This change has been sitting in my queue for a few years, I will adapt to the new reality. Thank you for pointing it out.

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WeakReferenceKey::equals and SoftReferenceKey::equals:

Instead of return Objects.equals(get(), obj);, suggest to do:

    return refersTo(obj);

@JimLaskey
Copy link
Member Author

JimLaskey commented Jul 6, 2023

@mlchung Not sure I catch the nuance. refersTo doesn't work if the search key is constructed. For example, if the key reference is a record Pair(int a, int b) {}, then map.containsKey(new Pair(10, 20)) would locate with equals but not with refersTo.

@RogerRiggs
Copy link
Contributor

@mlchung Not sure I catch the nuance. refersTo doesn't work if the search key is constructed. For example, if the key reference is a record Pair(int a, int b) {}, then map.containsKey(new Pair(10, 20)) would locate with equals but not with refersTo.

Using get() on a reference can momentarily create a strong reference to the item. refersTo() does not.
But since your map is holding proxies to the object, refersTo() would be to the proxy, not the object; so that's not an option.

@mlchung
Copy link
Member

mlchung commented Jul 6, 2023

@RogerRiggs WeakReferenceKey is a WeakReference with the key as the referent. I might be missing something?

@RogerRiggs
Copy link
Contributor

@RogerRiggs WeakReferenceKey is a WeakReference with the key as the referent. I might be missing something?

The comparison of the unwrapped key using refersTo is an identity comparison; but the usual comparison for Maps uses equals. Using refersTo would limit the use of ReferencedKeyMap to cases where the original key requires identity comparision. (I missed Jim's earlier comment that pointed this out.)

@mlchung
Copy link
Member

mlchung commented Jul 6, 2023

thanks. It may worth adding a comment about equality comparison and can't use refersTo

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thanks for the update. Nit: the test can use the 2-arg factory method.

test/jdk/jdk/internal/util/ReferencedKeyTest.java Outdated Show resolved Hide resolved
test/jdk/jdk/internal/util/ReferencedKeyTest.java Outdated Show resolved Hide resolved
Added intern with UnaryOperator<T> interning function to prepare key before adding to set.
Copy link
Member

@liach liach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the updates.

@JimLaskey
Copy link
Member Author

/integrate

@openjdk
Copy link

openjdk bot commented Jul 31, 2023

Going to push as commit 6af0af5.
Since your change was applied there have been 7 commits pushed to the master branch:

  • 86783b9: 8301996: Move field resolution information out of the cpCache
  • 5362ec9: 8312492: Remove THP sanity checks at VM startup
  • e47a84f: 8312489: Increase jdk.jar.maxSignatureFileSize default which is too low for JARs such as WhiteSource/Mend unified agent jar
  • 78f6799: 8293972: runtime/NMT/NMTInitializationTest.java#default_long-off failed with "Suspiciously long bucket chains in lookup table."
  • 97b6883: 8295059: test/langtools/tools/javap 12 test classes use com.sun.tools.classfile library
  • 3671d83: 8313252: Java_sun_awt_windows_ThemeReader_paintBackground release resources in early returns
  • b60e0ad: 8313207: Remove MetaspaceShared::_has_error_classes

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jul 31, 2023
@openjdk openjdk bot closed this Jul 31, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jul 31, 2023
@openjdk
Copy link

openjdk bot commented Jul 31, 2023

@JimLaskey Pushed as commit 6af0af5.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.


@Override
public boolean add(T e) {
return intern(e) == null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is wrong, as intern(…) will never return null.


This is the closest to the correct implementation,

Suggested change
return intern(e) == null;
return !contains(e) & intern(e) == e;

but may incorrectly return true in case of the following data race, assuming t1e == t2e:

  1. Thread 1 calls contains(t1e) and gets false.
  2. Thread 2 calls intern(t2e) and successfully adds t2e.
  3. Thread 1 calls intern(t1e) and gets back t2e, which is == to t1e.
  4. Thread 1 returns true, even though it was Thread 2 which modified the ReferencedKeySet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Your solution might be correct but I think !contains(e) is redundant since that is how intern starts out.

   static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) {
        T value = existingKey(setMap, key);
        if (value != null) {
            return value;
        }
        key = interner.apply(key);
        return internKey(setMap, key);
    }

Agree? So changing to return intern(e) == e; should be sufficient.

The other aspect of this is that internKey uses putIfAbsent which should prevent the race (assuming ConcurrentHashMap).

    /**
     * Attempt to add key to map.
     *
     * @param setMap    {@link ReferencedKeyMap} where interning takes place
     * @param key       key to add
     *
     * @param <T> type of key
     *
     * @return the old key instance if found otherwise the new key instance
     */
    private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
        ReferenceKey<T> entryKey = setMap.entryKey(key);
        T interned;
        do {
            setMap.removeStaleReferences();
            ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
            if (existing == null) {
                return key;
            } else {
                // If {@code putIfAbsent} returns non-null then was actually a
                // {@code replace} and older key was used. In that case the new
                // key was not used and the reference marked stale.
                interned = existing.get();
                if (interned != null) {
                    entryKey.unused();
                }
            }
        } while (interned == null);
        return interned;
    }

Agree? Anyone else want to pipe in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, intern(e) == e is sufficient.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While intern(e) == e is (mostly) sufficient, it will return a false positive when add(e) is called and e is already present in the map, the correctest implementation would be some internal API in ReferencedKeyMap for implementing ReferencedKeySet::add.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated
6 participants