-
Notifications
You must be signed in to change notification settings - Fork 245
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
fix(jsii-pacmak): computeIfAbsent throws ConcurrentModificationException #1706
Conversation
copy @iliapolo |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
🤩 wow |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
@NetaNir What did we change that suddenly caused this? Can you please include a reference to the PR that introduced this break? |
); | ||
this.code.line('String className = MODULE_TYPES.get(fqn);'); | ||
this.code.openBlock('if (!this.cache.containsKey(className))'); | ||
this.code.line('this.cache.put(className, this.findClass(className));'); |
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.
Any chance this can cause an infinite recursion (which I assume is why computeIfAbsent
throws this error)?
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 think that there is a circuit breaker here, but I'll let @RomainMuller comment
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 think this is going to be enough indeed. The problem was that further mutation to the map happened as part of this.findClass
within the lambda expression passed to computeIfAbsent
. This approach makes "atomic" mutations here (and this is safe because jsii
apps can be expected to be single-threaded).
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.
Is there a way to add a unit test for this?
We kinda knew that this would have to be the issue, but do we know HOW/WHY? How in the hell does (I also originally expected
But who can fill in the |
@rix0rrr to answer your
|
I'm trying to come up with a test that blows up before the fix is introduced. |
Got it. So that should be easy enough to write a test for then. So it's basically a matter of: class SomeClass {
public static const OTHER: OtherClass = new OtherClass();
} ? |
Although of course that won't break if |
Yeah that's what I thought but it appears the patch to trigger this situation depends on a bunch of other factors I'm trying to identify (possibly the properties being typed "any" or something. |
Oh I missed a critical element of the repro: it only happens with Java >= 9. It seems related to a change in class loading behavior between 8 and 9+. |
@RomainMuller Its not a change in class loading, its a change in the implementation of Regarding the repro, did you look at @NetaNir's repo? It wasn't that straight-forward to reproduce, but I do think the repro can probably be simplified. Also, since the recursion stems from |
So the flow is:
An easy way to trigger is to have a const property causing loading of an enum (it'll then try to resolve the enum when loading up it's values) Added a test to this effect. Interestingly, it works in Java 8, fails in Java 9. I suspect Java 9 resolves classes more eagerly than 8. |
|
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Ohhhhh |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! ❤️ I will now look into making sure the PR is up-to-date, then proceed to try and merge it! |
Merging (with squash)... |
Command |
Java generated modules use cache to store the result of
resolveClass
which is called when everjsiiStaticGet
/jsiiGet
is called. The cache is implemented as aHashMap
, to fill the cache we usecomputeIfAbsent
:When either of the get methods is called on a type which in itself includes a type, the first get (the root type get) will call get on the inner type. When
computeIfAbsent
is called from the root get, the compute method will trigger another call tocomputeIfAbsent
to resolve the inner type, since all types share the same cache it means that the innercomputeIfAbsent
call will modify the cache while the rootcomputeIfAbsent
is not yet completed, resulting it aConcurrentModificationException
.This effects all Java >= 9 applications as a change to the behavior of
computeIfAbsent
was introduced in Java 9. From the Javadocs:The use of
computeIfAbsent
was introduced in #1605 and released inv1.4.1
.This PR replaces
computeIfAbsent
with explicitly checking if the value exists in the cache and if not adding it (essentially old stylecomputeIfAbsent
).Test
Steps to verify fix:
jsii
andjsii-pacmak
.mvn
repo.Repro
A repro can be found in this repo
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.