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

Graalvm support #434

Closed
missourian55 opened this issue Jul 3, 2020 · 10 comments
Closed

Graalvm support #434

missourian55 opened this issue Jul 3, 2020 · 10 comments

Comments

@missourian55
Copy link

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\

@ben-manes
Copy link
Owner

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 examples folder would be appreciated.

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.

@missourian55
Copy link
Author

missourian55 commented Jul 3, 2020

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>

@richvim
Copy link

richvim commented May 14, 2021

I think this is currently blocked by: oracle/graal#3028

@mmoayyed
Copy link

@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 Java(TM) SE Runtime Environment GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07) continues to reproduce this issue, without the noted config above.

@ben-manes
Copy link
Owner

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.

@vlsi
Copy link
Contributor

vlsi commented Jan 17, 2023

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.

@ben-manes
Copy link
Owner

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 .

@linghengqian
Copy link

I think this is currently blocked by: oracle/graal#3028

@ben-manes
Copy link
Owner

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)

@linghengqian
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants