-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Graalvm support #434
Comments
I am not familiar with how to build and use GraalVM. However you can look at Micronaut and Quarkus for examples, e.g. this reflection-config.json might be a hint. A contribution to add to our Due to the number of features, there are many possible fields on both the cache and entry. This could be wasteful when a feature is not used, such as timestamps, leading to a lot of unnecessary overhead. We try to code generate to specialized types in order to save runtime memory at the cost of a large on-disk footprint. This assumes unloaded classes cost nothing, which is generally the case. Unfortunately referencing all of the classes explicitly to instantiate, e.g. a switch to constructors, does have a high class loading penalty for the factory. We therefore use reflection to find and load the class, which is less expensive and only used when creating a cache instance. Since GraalVM requires knowing every class upfront it's dead code elimination removes these packaged classes. It sounds as if you need to whitelist them in a [reflection-config.json] file. I am not sure if there is anything for us to bundle in our jar to assist Graal, e.g. a full version of this configuration. Since the consumers like Graal and Quarkus have handled it themselves I'm guessing it isn't our responsibility. Any clarity here would be helpful. |
I appreciate the pointers & explanation. In my case to make below cache work as a native image. I added three more entries in my reflection-config.json. Caffeine is one of the valuable open source library of java ecosystem thanks a lot for the contribution. public Cache<String, MyObject> caffeine() {
return Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
} [
{
"name": "com.github.benmanes.caffeine.cache.PSW",
"allDeclaredConstructors": true
},
{
"name": "com.github.benmanes.caffeine.cache.PSWMS",
"allDeclaredConstructors": true
},
{
"name": "com.github.benmanes.caffeine.cache.SSLA",
"allDeclaredConstructors": true
},
{
"name": "com.github.benmanes.caffeine.cache.SSLMSW",
"allDeclaredConstructors": true
},
{
"name": "com.github.benmanes.caffeine.cache.SSMSW",
"allDeclaredConstructors": true
}
] <plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<configuration>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
<additionalBuildArgs>
--allow-incomplete-classpath,
--enable-all-security-services,
--report-unsupported-elements-at-runtime,
-H:+RemoveSaturatedTypeFlows,
-H:ReflectionConfigurationFiles=reflection-config.json,
-H:EnableURLProtocols=https
</additionalBuildArgs>
</configuration>
</execution>
</executions>
</plugin> |
I think this is currently blocked by: oracle/graal#3028 |
@ben-manes apologies for resurrecting this issue; as you might of course know, there is now https://github.com/oracle/graalvm-reachability-metadata which collects graalvm metadata about libraries to assist with native builds. It might make sense to include and ship a reflection-config.json type of file that contains the correct entries. Developers/users today either have to host this file directly for each service they use, or at some point this might end up in graalvm-reachability-metadata. While this is no big deal, I think the preference/recommendation is to always work with library authors and maintainers so things are supported as OOTB as possible. Building native images with the latest caffein version and |
I feel like this is a messy area that could get frustrating as a library author due to its immaturity, my lack of experience with it, and inconsistent (or stale) advice from experts. The problem here is that cache uses code generation to minimize the memory overhead, e.g. only having a per-entry timestamp field if expiration is enabled. An unloaded class has no runtime cost and dynamic class loading was a core design feature of Java, so this is an old optimization trick since early times. AOT breaks this and likely that means it violates parts of the spec as not quite Java anymore (hence Project Leyden). Previously Quarkus asked for the predecessor to this, a jandex file, but they were dissatisfied with the result due to the jar size so we removed it. They claimed it added 14mb of bloat, though a recent user said it was closer to 1.5mb. Either way, it may be that including this negates the special cases that frameworks use to include and causes larger than intended binaries. Given that it is not very common or users typically do it through a framework that handles it for them, I'm inclined to not do anything here for the time being. |
Another possibility is to package Caffeine into several jar files (~jar per cache variation 😂, and one for the common code), then users could narrow down the dependencies they need. |
That would be a nightmare for most users who might adjust settings in a configuration file and have surprising runtime errors. Ideally then we'd try to redesign the builder to statically include what is needed upfront, but that would be uglier and confusing. There are mixed scenarios like TTL + maxSize + weakKeys, so hundreds of variations. To test these all work together results in a combinatorial factor of different options, so our parameterized testing results in over 10M+ executions. AOT users opt into this dance of selective inclusions (e.g. proguard) as a tradeoff they intentionally make, whereas most users would consider it unnecessary complexity . |
|
We have tests that run using junit 3-5 (external suites) and testng (internal suites), which are run as separate gradle tasks. If helpful, I think we could isolate the native tests under a custom configuration to reduce the test dependencies. Unfortunately JUnit 5 is not robust to large test suites (e.g. memory leaks) and it's compatibility shims do not work very well (e.g. broken scanning for TestNG). They caught up to be comparable in terms of features so it would be feasible to migrate if they can fix their performance problems. That's not drawn much interest by their team and, since TestNG has been great for us, I think it will remain as is. (I previously looked into this for taking advantage of reporting and features of Gradle Enterprise) |
|
I am getting below error when running my application in native mode. Anything I can do to make it work?
text \nCaused by: java.lang.ClassNotFoundException: com.github.benmanes.caffeine.cache.SSLMSW com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60) java.lang.ClassLoader.loadClass(ClassLoader.java:233)\n\tat com.github.benmanes.caffeine.cache.LocalCacheFactory.newBoundedLocalCache(LocalCacheFactory.java:95)\n\t... 27 more\
The text was updated successfully, but these errors were encountered: