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

Support for GraalVM Native Images #1316

Closed
ilopmar opened this issue Jun 22, 2020 · 12 comments
Closed

Support for GraalVM Native Images #1316

ilopmar opened this issue Jun 22, 2020 · 12 comments
Labels
type: feature A new feature
Milestone

Comments

@ilopmar
Copy link

ilopmar commented Jun 22, 2020

Feature Request

It would be nice to make the library compatible with GraalVM out of the box.

Is your feature request related to a problem? Please describe

Currently is not possible to convert a Java app with Lettuce to a GraalVM native image.

Describe the solution you'd like

The library should include all the necessary GraalVM configuration so users don't need to do anything on their own applications. This may also imply doing so modifications in the library to make it compatible.

Describe alternatives you've considered

I've been playing a little bit with this and I've create a simple plain Java application that uses the library to connect to Redis and execute a few commands. The application is available at: https://github.com/ilopmar/redis-lettuce-graalvm, please take a look at the README about how to run the app.
I don't have experience neither with Redis nor the library so I'm pretty sure that the code can be improved a lot. Please feel free to do it :-)

Things to consider:

  • I've used the GraalVM Tracing Agent (see this) to help with the generation of all the GraalVM configuration. Take a look at the files jni-config.json, proxy-config.json and reflect-config.json here.

  • At this moment, with the only things that my sample application uses from the library, all those previous GraalVM reflection configuration is needed. I'm not 100% sure if all of that is really necessary because I don't know the internals of the library.

  • It is only possible to generate the native image adding the flag --report-unsupported-elements-at-runtime (see this). The GraalVM team discourage the use of that option so probably some changes in the library are needed to being able to remove it.

  • I also needed to add org.hdrhistogram:HdrHistogram and org.latencyutils:LatencyUtils dependencies to be able to run the native-image. Without them the process fails when starting with the following error. I don't know why they are mandatory because I'm able to run the application with the JVM without them.

$ ./redis-lettuce-graal
Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:290)
	at java.lang.Class.ensureInitialized(DynamicHub.java:499)
	at io.lettuce.core.resource.DefaultClientResources.<init>(DefaultClientResources.java:181)
	at io.lettuce.core.resource.DefaultClientResources$Builder.build(DefaultClientResources.java:530)
	at io.lettuce.core.resource.DefaultClientResources.create(DefaultClientResources.java:231)
	at io.lettuce.core.AbstractRedisClient.<init>(AbstractRedisClient.java:97)
	at io.lettuce.core.RedisClient.<init>(RedisClient.java:90)
	at io.lettuce.core.RedisClient.create(RedisClient.java:139)
	at example.App.main(App.java:9)
Caused by: java.lang.RuntimeException: java.lang.NoSuchFieldException: pauseDetectorWrapper
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:127)
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
	at io.lettuce.core.metrics.DefaultCommandLatencyCollector.<clinit>(DefaultCommandLatencyCollector.java:52)
	at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
	... 8 more
Caused by: java.lang.NoSuchFieldException: pauseDetectorWrapper
	at java.lang.Class.getDeclaredField(DynamicHub.java:2070)
	at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:122)

A good starting point could be to add more different usages of the library (which I don't know) to the sample aplicaiton and try to generate the native image. If it works maybe you can include that generated configuration in the library jars so users don't need to.
Also the documentation should be updated because, when I added a Command (see this) I also needed to include it in proxy-config.json (see this) and in reflect-config.json (see this). I'm not really sure if the library could create that configuration automatically for the users, so at least, for a first version of the GraalVM support this should be documented.

Teachability, Documentation, Adoption, Migration Strategy

With the changes to support GraalVM included in the library, users would be able to convert their applications that use it to GraalVM native-images. Also frameworks like Micronaut, Spring,... and other would also benefit from this.

@ilopmar ilopmar added the type: feature A new feature label Jun 22, 2020
@mp911de
Copy link
Collaborator

mp911de commented Jun 22, 2020

Lettuce requires a bit of reflection and proxy configuration to properly build for Graal native images. DefaultCommandLatencyCollector must be initialized at runtime.

Happy to add Graal hints and update the documentation. I’m not super familiar with Graal so any kind of support is appreciated.

@ilopmar
Copy link
Author

ilopmar commented Jun 22, 2020

I've done a quick test removing the HdrHistogram and LatencyUtils dependencies and adding the option --initialize-at-run-time=io.lettuce.core.metrics.DefaultCommandLatencyCollector but the application fails with the same error when starting it.

@mp911de mp911de changed the title Support for GraalVM Support for GraalVM Native Images Jun 22, 2020
christophstrobl added a commit to christophstrobl/lettuce-core that referenced this issue Jun 23, 2020
@mp911de
Copy link
Collaborator

mp911de commented Jun 23, 2020

@ilopmar we received today a PR with native image configuration. Do you mind having a look at #1318? I went through the config on your side and added the missing elements in the comments so we can merge both sources for the Graal native image config.

Going ahead, I'd refrain from adding netty-specifics as they rather belong into netty itself unless we're applying reflection to netty types.

@ilopmar
Copy link
Author

ilopmar commented Jun 23, 2020

@mp911de I put some comments there. As I said before, once you have a PR ready, ping me and I will test it :-)

mp911de pushed a commit that referenced this issue Jun 23, 2020
mp911de added a commit that referenced this issue Jun 23, 2020
Extend reflection and proxy configuration. Add documentation

Original pull request: #1318.
mp911de pushed a commit that referenced this issue Jun 23, 2020
mp911de added a commit that referenced this issue Jun 23, 2020
Extend reflection and proxy configuration. Add documentation

Original pull request: #1318.
@mp911de mp911de added this to the 5.3.2 milestone Jun 23, 2020
@mp911de
Copy link
Collaborator

mp911de commented Jun 23, 2020

The first batch of GraalVM Native config files is merged to Lettuce via #1318. Documentation is available from https://github.com/lettuce-io/lettuce-core/wiki/Using-Lettuce-with-Native-Images.

5.3.2 and 6.0.0 snapshot are available from OSS Sonatype snapshot repositories. Care to give it a test?

@ilopmar
Copy link
Author

ilopmar commented Jun 23, 2020

I've tried the 5.3.2 snapshot with my simple test application (the one I shared when I open this issue). I've removed all the Lettuce config (now included in the library) and kept the rest of the GraalVM config (including the one for Netty).

I'm only able to create the native-image if I use the flag --report-unsupported-elements-at-runtime. Without it I get the following error while generating the native-image:

[redis-lettuce-graal:7526]    classlist:   4,840.41 ms,  1.63 GB
[redis-lettuce-graal:7526]        (cap):     993.94 ms,  1.63 GB
WARNING: Could not resolve org.HdrHistogram.Histogram for reflection configuration.
WARNING: Could not resolve org.LatencyUtils.PauseDetector for reflection configuration.
[redis-lettuce-graal:7526]        setup:   3,154.24 ms,  1.63 GB
[redis-lettuce-graal:7526]     (clinit):     678.84 ms,  2.71 GB
[redis-lettuce-graal:7526]   (typeflow):  17,730.55 ms,  2.71 GB
[redis-lettuce-graal:7526]    (objects):  18,157.80 ms,  2.71 GB
[redis-lettuce-graal:7526]   (features):     781.88 ms,  2.71 GB
[redis-lettuce-graal:7526]     analysis:  39,112.80 ms,  2.71 GB
Error: Unsupported features in 5 methods
Detailed message:
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.Class.getConstantPool() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.System$2.getConstantPool(System.java:1230)
Call path from entry point to java.lang.System$2.getConstantPool(Class): 
	at java.lang.System$2.getConstantPool(System.java:1230)
	at sun.reflect.annotation.TypeAnnotationParser.parseAllTypeAnnotations(TypeAnnotationParser.java:323)
	at sun.reflect.annotation.TypeAnnotationParser.fetchBounds(TypeAnnotationParser.java:299)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:244)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:237)
	at sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getAnnotatedBounds(TypeVariableImpl.java:241)
	at com.oracle.svm.reflect.TypeVariable_getAnnotatedBounds_88ea462da2b17ea45028db307519aaeeec9a4036_851.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.lettuce.core.dynamic.support.TypeWrapper$TypeProxyInvocationHandler.invoke(TypeWrapper.java:215)
	at io.lettuce.core.support.$Proxy137.close(Unknown Source)
	at example.App.main(App.java:18)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:149)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:184)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.Class.getExecutableTypeAnnotationBytes(Executable) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.System$2.getRawExecutableTypeAnnotations(System.java:1248)
Call path from entry point to java.lang.System$2.getRawExecutableTypeAnnotations(Executable): 
	at java.lang.System$2.getRawExecutableTypeAnnotations(System.java:1248)
	at sun.reflect.annotation.TypeAnnotationParser.parseAllTypeAnnotations(TypeAnnotationParser.java:318)
	at sun.reflect.annotation.TypeAnnotationParser.fetchBounds(TypeAnnotationParser.java:299)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:244)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:237)
	at sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getAnnotatedBounds(TypeVariableImpl.java:241)
	at com.oracle.svm.reflect.TypeVariable_getAnnotatedBounds_88ea462da2b17ea45028db307519aaeeec9a4036_851.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.lettuce.core.dynamic.support.TypeWrapper$TypeProxyInvocationHandler.invoke(TypeWrapper.java:215)
	at io.lettuce.core.support.$Proxy137.close(Unknown Source)
	at example.App.main(App.java:18)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:149)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:184)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.Class.getRawTypeAnnotations() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.System$2.getRawClassTypeAnnotations(System.java:1245)
Call path from entry point to java.lang.System$2.getRawClassTypeAnnotations(Class): 
	at java.lang.System$2.getRawClassTypeAnnotations(System.java:1245)
	at sun.reflect.annotation.TypeAnnotationParser.parseAllTypeAnnotations(TypeAnnotationParser.java:315)
	at sun.reflect.annotation.TypeAnnotationParser.fetchBounds(TypeAnnotationParser.java:299)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:244)
	at sun.reflect.annotation.TypeAnnotationParser.parseAnnotatedBounds(TypeAnnotationParser.java:237)
	at sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getAnnotatedBounds(TypeVariableImpl.java:241)
	at com.oracle.svm.reflect.TypeVariable_getAnnotatedBounds_88ea462da2b17ea45028db307519aaeeec9a4036_851.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.lettuce.core.dynamic.support.TypeWrapper$TypeProxyInvocationHandler.invoke(TypeWrapper.java:215)
	at io.lettuce.core.support.$Proxy137.close(Unknown Source)
	at example.App.main(App.java:18)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:149)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:184)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported type java.lang.invoke.MemberName is reachable: All methods from java.lang.invoke should have been replaced during image building.
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.invoke.MethodHandles$Lookup.findSpecial(MethodHandles.java:1004)
Call path from entry point to java.lang.invoke.MethodHandles$Lookup.findSpecial(Class, String, MethodType, Class): 
	at java.lang.invoke.MethodHandles$Lookup.findSpecial(MethodHandles.java:1002)
	at io.lettuce.core.internal.DefaultMethods$MethodHandleLookup$2.lookup(DefaultMethods.java:92)
	at io.lettuce.core.internal.DefaultMethods.lookupMethodHandle(DefaultMethods.java:49)
	at io.lettuce.core.dynamic.intercept.DefaultMethodInvokingInterceptor.lookupMethodHandle(DefaultMethodInvokingInterceptor.java:59)
	at io.lettuce.core.dynamic.intercept.DefaultMethodInvokingInterceptor$$Lambda$269/1545533889.apply(Unknown Source)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.SpinedBuffer.forEach(SpinedBuffer.java:246)
	at java.util.stream.SpinedBuffer.toString(SpinedBuffer.java:269)
	at java.lang.String.valueOf(String.java:2994)
	at java.nio.charset.IllegalCharsetNameException.<init>(IllegalCharsetNameException.java:55)
	at java.nio.charset.Charset.checkName(Charset.java:315)
	at com.oracle.svm.core.jdk.Target_java_nio_charset_Charset.lookup(CharsetSubstitutions.java:78)
	at java.nio.charset.Charset.isSupported(Charset.java:505)
	at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_ARRAY:Ljava_nio_charset_Charset_2_0002eisSupported_00028Ljava_lang_String_2_00029Z(generated:0)
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported type java.lang.invoke.MemberName is reachable: All methods from java.lang.invoke should have been replaced during image building.
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
	at parsing java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1233)
Call path from entry point to java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(Method, Class): 
	at java.lang.invoke.MethodHandles$Lookup.unreflectSpecial(MethodHandles.java:1231)
	at io.lettuce.core.internal.DefaultMethods$MethodHandleLookup$1.lookup(DefaultMethods.java:71)
	at io.lettuce.core.internal.DefaultMethods.lookupMethodHandle(DefaultMethods.java:49)
	at io.lettuce.core.dynamic.intercept.DefaultMethodInvokingInterceptor.lookupMethodHandle(DefaultMethodInvokingInterceptor.java:59)
	at io.lettuce.core.dynamic.intercept.DefaultMethodInvokingInterceptor$$Lambda$269/1545533889.apply(Unknown Source)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.SpinedBuffer.forEach(SpinedBuffer.java:246)
	at java.util.stream.SpinedBuffer.toString(SpinedBuffer.java:269)
	at java.lang.String.valueOf(String.java:2994)
	at java.nio.charset.IllegalCharsetNameException.<init>(IllegalCharsetNameException.java:55)
	at java.nio.charset.Charset.checkName(Charset.java:315)
	at com.oracle.svm.core.jdk.Target_java_nio_charset_Charset.lookup(CharsetSubstitutions.java:78)
	at java.nio.charset.Charset.isSupported(Charset.java:505)
	at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_ARRAY:Ljava_nio_charset_Charset_2_0002eisSupported_00028Ljava_lang_String_2_00029Z(generated:0)

Where you able to generate the native-image without that flag?

@mp911de
Copy link
Collaborator

mp911de commented Jun 23, 2020

I haven’t removed that flag so far. In fact, I had to upgrade my GraalVM because that one I had installed (Version 19) didn’t work at all. I downloaded the latest release that was released in May.

@mp911de
Copy link
Collaborator

mp911de commented Jun 24, 2020

I retried without --report-unsupported-elements-at-runtime and receive the same issue. Currently, we cannot do anything about MethodHandles as command interfaces can make use of default interface methods and we have no alternative to invoke these. Regarding the annotation errors, we're synthesizing annotations during runtime and I have no clue how to fix that issue.

For the time being, please use --report-unsupported-elements-at-runtime unless someone submits a pull request that is able to fix those issues.

@ilopmar
Copy link
Author

ilopmar commented Jun 24, 2020

Thanks for the explanation. I asked because I didn't see that option mention on the documentation and without it, at this moment is not possible to create the native-image. That would surprise users trying to do it.

@mp911de
Copy link
Collaborator

mp911de commented Jun 24, 2020

Okay, let me add it to the docs.

@ilopmar
Copy link
Author

ilopmar commented Jun 24, 2020

Excellent! Thank you very much for your help. I'll include all the GraalVM configuration in Micronaut-Redis and when you release next version I'll remove it! :-)

@mp911de
Copy link
Collaborator

mp911de commented Jun 24, 2020

5.3.2 is scheduled for Mid/Late July. Thanks everyone for your kind support. Closing this ticket as solved.

@mp911de mp911de closed this as completed Jun 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature A new feature
Projects
None yet
Development

No branches or pull requests

2 participants