-
Notifications
You must be signed in to change notification settings - Fork 874
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
Conversation
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. |
@neilcsmith-net You were right, |
Thanks! No need to hold up review or merge of this pending NB19 now. 😄 |
platform/o.n.bootstrap/src/org/netbeans/ProxyClassPackages.java
Outdated
Show resolved
Hide resolved
if (!recurse) { | ||
return null; | ||
} | ||
synchronized(packages) { | ||
Package pkg = packages.get(name); | ||
if (pkg != null) { | ||
return pkg; | ||
} | ||
if (!recurse) { | ||
return null; | ||
} |
There was a problem hiding this comment.
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
String pkg = it.next(); | ||
Set<ProxyClassLoader> set = ProxyClassPackages.packageCoverage.get(pkg); | ||
static void removeCoveredPakcages(ProxyClassLoader loader) { | ||
packageCoverage.values().removeIf( (Set<ProxyClassLoader> set) -> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
return null; | ||
} | ||
synchronized(packages) { | ||
pkg = packages.get(name); | ||
String path = name.replace('.', '/'); |
There was a problem hiding this comment.
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.
f1104ae
to
0f08732
Compare
NetBeans Notes:
For ProxyClassLoader class:
packages
map and remove some synchronized blocksARBITRARY_LOAD_WARNINGS
setFor ProxyClassPackages class:
PACKAGE_COVERAGE
map and remove the synchronized methods for get and remove operationsPACKAGE_COVERAGE
map for the add operation. It needs to be linearizable.Testing:
platform.o.n.bootstrap