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

fix(java): compilation fails with "code too large" #1605

Merged
merged 1 commit into from
Apr 22, 2020

Conversation

RomainMuller
Copy link
Contributor

@RomainMuller RomainMuller commented Apr 22, 2020

Commit Message

fix(java): compilation fails with "code too large" (#1605)

In cases where a module includes a large amount of types, compilation of
Java packages could fail with the "code too large" error. This is due to
the presence of a single method in the $Module class that contains a
reference to all types in the package, resulting in a large amount of
generated bytecode (more than Java's maximum of 64KB).

In order to remediate, moved the FQN to Class mapping to a resource file
that gets loaded when the $Module class is initialized. The individual
Class entries are then resolved at the last minute using
Class.forName to ensure the reoslution happens after the full
mapping has been processed.

End Commit Message


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

In cases where a module includes a large amount of types, compilation of
Java packages could fail with the "code too large" error. This is due to
the presence of a single method in the `$Module` class that contains a
reference to all types in the package, resulting in a large amount of
generated bytecode (more than Java's maximum of 64KB).

In order to remediate, moved the FQN to Class mapping to a resource file
that gets loaded when the `$Module` class is initialized. The individual
`Class` entries are then resolved at the last minute using
`Class.forName` to ensure the reoslution happens *after* the full
mapping has been processed.
@RomainMuller RomainMuller self-assigned this Apr 22, 2020
@RomainMuller RomainMuller requested a review from a team April 22, 2020 15:20
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Apr 22, 2020
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-Blkkw9bQFn8A
  • Commit ID: c3d2e09
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Apr 22, 2020

Thank you for contributing! ❤️ I will now look into making sure the PR is up-to-date, then proceed to try and merge it!

@mergify mergify bot added the pr/ready-to-merge This PR is ready to be merged. label Apr 22, 2020
@mergify mergify bot merged commit b9ec853 into master Apr 22, 2020
@mergify mergify bot deleted the rmuller/java-code-too-large branch April 22, 2020 16:02
@mergify
Copy link
Contributor

mergify bot commented Apr 22, 2020

Merging (with squash)...

@mergify mergify bot removed the pr/ready-to-merge This PR is ready to be merged. label Apr 22, 2020
private static final Map<String, String> MODULE_TYPES = load();

private static Map<String, String> load() {
final Map<String, String> result = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably makes sense to implement this in JsiiModule, no?

mergify bot pushed a commit that referenced this pull request Jun 2, 2020
…ion (#1706)

---

Java generated modules use cache to store the result of `resolveClass` which is called when ever `jsiiStaticGet`/`jsiiGet` is called. The cache is implemented as a  `HashMap`, to fill the cache we use `computeIfAbsent`:

```Java
this.cache.computeIfAbsent(MODULE_TYPES.get(fqn), this::findClass);
```
> [code](https://github.com/aws/jsii/blob/master/packages/jsii-pacmak/lib/targets/java.ts#L2533)

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 to `computeIfAbsent` to resolve the inner type, since all types share the same cache it means that the inner `computeIfAbsent` call will modify the cache while the root `computeIfAbsent` is not yet completed, resulting it a `ConcurrentModificationException`. 

This effects all Java >= 9 applications as a change to the behavior of `computeIfAbsent` was introduced in Java 9.  From the [Javadocs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/HashMap.html#computeIfAbsent(K,java.util.function.Function)):

```
Throws:
ConcurrentModificationException - if it is detected that the mapping function modified this map
```
The use of  `computeIfAbsent `  was introduced in #1605 and released in `v1.4.1`. 


This PR replaces `computeIfAbsent` with explicitly checking if the value exists in the cache and if not adding it (essentially old style `computeIfAbsent`).

### Test
Steps to verify fix:

1. Build and package a sample module (see repro) using a local version of `jsii` and `jsii-pacmak`.
2. Add the generated jar to a local `mvn` repo.
3. Added the package as a dependency to a Java11 application.
4. Verify the exception is no longer thrown. 

### Repro

A repro can be found in this [repo](https://github.com/NetaNir/jsii-nested-get-repro)


By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license].

[Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants