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

Use ConcurrentMap/ConcurrentHashMap.newKeySet instead of Map/Collections.synchronizedSet #6309

Merged
merged 1 commit into from
Sep 30, 2023

Conversation

pepness
Copy link
Member

@pepness pepness commented Aug 7, 2023

NetBeans Notes:

For ProxyClassLoader class:

  • Use ConcurrentMap for packages map and remove some synchronized blocks
  • Use ConcurrentHashMap.newKeySet() instead of Collections.synchronizedSet for ARBITRARY_LOAD_WARNINGS set
  • Remove unused variables

For ProxyClassPackages class:

  • Use ConcurrentMap for PACKAGE_COVERAGE map and remove the synchronized methods for get and remove operations
  • Use synchronized on the PACKAGE_COVERAGE map for the add operation. It needs to be linearizable.

Testing:

  • Full build done
  • Verify successful execution of libraries and licenses Ant test
  • Verify successful execution of unit tests for module platform.o.n.bootstrap
  • Verify successful execution of sigtests
  • Verify successful execution of commit-validation
  • Worked normally with various projects.

@pepness pepness added the ci:all-tests [ci] enable all tests label Aug 7, 2023
@pepness pepness added this to the NB20 milestone Aug 7, 2023
@pepness pepness self-assigned this Aug 7, 2023
@pepness pepness changed the title For ProxyClassLoader: Use ConcurrentMap/ConcurrentHashMap.newKeySet instead of Map/Collections.synchronizedSet Aug 7, 2023
@neilcsmith-net neilcsmith-net added the do not merge Don't merge this PR, it is not ready or just demonstration purposes. label Aug 7, 2023
@neilcsmith-net
Copy link
Member

Please don't merge sig file changes pending final merge of #6252

I'm not sure there's any need to include the sig file change at all in this PR? Any changes will be picked up in the API snapshot for NB20 automatically.

@pepness
Copy link
Member Author

pepness commented Aug 7, 2023

@neilcsmith-net You were right, ant check-sigtests run successfully without the need of regen the signatures, only when the ant check-sigtests fails one should regen the signatures.

@neilcsmith-net neilcsmith-net removed the do not merge Don't merge this PR, it is not ready or just demonstration purposes. label Aug 7, 2023
@neilcsmith-net
Copy link
Member

Thanks! No need to hold up review or merge of this pending NB19 now. 😄

Comment on lines 471 to 478
if (!recurse) {
return null;
}
synchronized(packages) {
Package pkg = packages.get(name);
if (pkg != null) {
return pkg;
}
if (!recurse) {
return null;
}
Copy link
Member

@mbien mbien Aug 11, 2023

Choose a reason for hiding this comment

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

this alters the behavior. The recursive check has to be after the first get and return.

I think you could do this however:

        Package pkg = packages.get(name);
        if (pkg != null) {
            return pkg;
        }
        if (!recurse) {
            return null;
        }
        synchronized(packages) {
            pkg = packages.get(name);
            String path = name.replace('.', '/');
            ...
        

essentially calling get twice, with a lock free path for the non-recursive case

@sdedic sdedic self-requested a review August 16, 2023 08:06
String pkg = it.next();
Set<ProxyClassLoader> set = ProxyClassPackages.packageCoverage.get(pkg);
static void removeCoveredPakcages(ProxyClassLoader loader) {
packageCoverage.values().removeIf( (Set<ProxyClassLoader> set) -> {
Copy link
Member

Choose a reason for hiding this comment

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

I was not sure about this one, the add/remove concurrency would yield IMHO the same results as the original code, but find is allowed to see an intermediate results, with just some matching classloaders removed. This is probably not a big deal however provided that data for a single package is consistent; there's no query across different packages.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right, it would be best to add a synchronzed(packageCoverage) { packageCoverage.values().removeIf... }.

String pkg = it.next();
Set<ProxyClassLoader> set = ProxyClassPackages.packageCoverage.get(pkg);
static void removeCoveredPakcages(ProxyClassLoader loader) {
packageCoverage.values().removeIf( (Set<ProxyClassLoader> set) -> {
if (set.contains(loader) && set.size() == 1) {
Copy link
Member

Choose a reason for hiding this comment

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

This inner set is not synchronized - this should be synchronized with possible add work on the collection value -- especially the decision based on size().

Copy link
Member Author

Choose a reason for hiding this comment

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

The synchronzed(packageCoverage) { packageCoverage.values().removeIf... } would solve this right?

Copy link
Member

Choose a reason for hiding this comment

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

Hm ... it does. But I forgot the potential race between findCoveredPkg(String pkg) and add/remove operation. It is present even in the current code !!

Imagine (even with the current code) findCoveredPkg returning a live Set from the internal strucutre, and then, in parallel to the reading thread, add adds a new package to the set.

It's OK for transition between 0-1-many loaders, but unsafe if there are 2+ loaders and the set is being changed. A copy-on-write in add/remove seems as the best solution: gets greatly outnumber the mutations.

Copy link
Member Author

Choose a reason for hiding this comment

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

@sdedic you are right, the current code has potential race problems.

But I forgot the potential race between findCoveredPkg(String pkg) and add/remove operation. It is present even in the current code !!

To prevent this I found out that the computeIfPresent method implementation from ConcurrentHashMap synchronize the field that is reading to prevent consistency issues with the other methods that are modifying the Map (removeCoveredPakcages and addCoveredPackages), so hopefully, there should not be any more race or consistency issues.

@pepness pepness requested a review from sdedic August 24, 2023 17:33
return null;
}
synchronized(packages) {
pkg = packages.get(name);
String path = name.replace('.', '/');
Copy link
Member

Choose a reason for hiding this comment

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

Probably missing another if (pkg != null) this time after entering this critical section.

- Use ConcurrentMap for 'packages' map and remove some synchronized blocks
- Use ConcurrentHashMap.newKeySet() instead of
  Collections.synchronizedSet for 'ARBITRARY_LOAD_WARNINGS' Set
- Move some logic outside a sync block in 'getPackageFast' method
- Remove unused variables
For ProxyClassPackages:
- Use ConcurrentMap for 'PACKAGE_COVERAGE' map and remove the
  synchronized methods for the get operation
- Use synchronized on the 'PACKAGE_COVERAGE' map for the add and remove
  operations, they need to be linearizable.
- The 'findCoveredPkg' method should return a consistent value. The
  'computeIfPresent' implementation from ConcurrentHashMap
  synchronize the field that is reading to prevent consistency issues
  with the other methods (add or remove) modifying the Map.

Thanks to mbien, neilcsmith, and sdedic.
@pepness pepness merged commit 8dc5e34 into apache:master Sep 30, 2023
@mbien mbien added the Platform [ci] enable platform tests (platform/*) label Oct 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci:all-tests [ci] enable all tests Platform [ci] enable platform tests (platform/*)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants