-
Notifications
You must be signed in to change notification settings - Fork 258
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
[Java] Support GraalVM Native Image #678
Comments
@aigens Thanks for submitting this issue. This is an interesting topic, Fury does not support graalvm yet, maybe we should add support for it, considering it's starting to become popular. Could you share more details about your solution? |
As microservice and serverless getting more popular, people are switching to lighter frameworks that uses less memory and have quicker startup time. We are switching to native java for higher efficiency. Goal of using native java align very well with what Fury trying to achieve. I believe supporting it will further make Fury more modern and developer friendly. Framework that we are using that support compiling to native java with Graalvm: Looking forward to Fury! |
@aigens I see, Fury do should support graalvm. But I'm not sure whether graalvm can load bytecodes into class dynamically, Fury will generate serializers class dynamically. Graalvm native image seems doesn' support it? Also we should build reflection table for graalvm too. |
@chaokunyang native images don't have a JIT and can't dynamically load bytecode at runtime. You'd have to go with static code generation before compilation. In your place I'd add an annotation for each serializable class (maybe with a list?) and create an annotation processor that outputs the generated code as Java files in the generated-sources directory, e.g., @FurySerializable
public class RegisteredClass {
private ExternalDependency someField; // ExternalDependency.class automatically added
}
@FuryConfig( classes = { RegisteredClass.class, ExternalDependency.class }
public class ExplicitFuryConfig {
} After that you can create a config and use the service loader interface to register it at runtime. Here is a simple example for an annotation processor that creates GraalVM config files: https://github.com/ennerf/NativeImageConfigGenerator |
Disclaimer: I am a member of the Quarkus team
This is not supported in GraalVM (and to the extent of my knowledge, never will be as it is antithetical to the whole closed-world principle that GraalVM is based on). The typical way something like this would be handled is to have the serializers generated at build time (this is actually what Kotlin serialization does). Depending on what exactly you want to achieve, there are various ways to do this. |
Hi @ennerf @geoand thanks for your inputs, it's very valuable. If I understand right, there are two ways to support native image:
|
@geoand Fury supports generate Java code or bytecode. If we generate Java code, will it affect kotlin build? |
Generating source code would also be problematic for Kotlin projects AFAIK. I don't know how important it is for the project to support Kotlin, I am just mentioning it for completeness as we have a lot of Kotlin usage in Quarkus. |
@geoand It would be great if fury can integrate with Quarku. Quarkus is an amazing Project. Integration with it will make fury reach for more users. And hopefully make Quarkus further faster. I don't have much graalvm experience. It may take some time before I can start this work. |
👍🏼 |
It's our target to support all common Jvm languages like kotlin/scala/groovy/etc. I believe those languages are supported already in fury for traditional jdk. Maybe there are still some optimization such as #765 . So we do need to support kotlin well in graal. |
You can manually generate all the reflection configurations if you first run everything with the graal agent and then compile against that |
@Ran-Mewo Fury uses But for initializing fury at build time, I'm not sure whether graal allow us to invoke fury to generate java code at build time. |
I suppose so? Due to me seeing the class Have both of the stacktraces for run time and build time Initializing at run time:
Initializing at build time
(Also yes I know it said to do |
@Ran-Mewo For first exception |
Well at this point we'll just have to try it and see |
@Ran-Mewo I'll try it tomorrow and give the feedbacks to you. |
You will not be able to generate and execute bytecode at runtime without a JIT. Afaik GraalVM provides an option to bundle a JIT within a native image (Espresso), but that adds extra overhead and sort of defeats the purpose of AOT. Your best bet is probably to generate the classes at compile time, which seems to be what everyone else is doing. |
+1 |
I read the main graalvm doc. One of the last thing remained is Unsafe offset:
It seems that the offset is subject to change between build time and runtime. I didn't find the doc explain why the offset change between build time and runtime, it's just a relative offset. Fury inline such offsets in the generated code, need to change to a compile-time constant of the generated class. |
The doc https://build-native-java-apps.cc/developer-guide/substitution/ demonstrat how to substitute unsafe offset. But introduce extra graalvm depdency and extra complexities. |
@ennerf You mean generate the classes at graal image build time? Or generate classes ahead before pass to graal for compilation. |
I try to generate serializer code at build time, but got following error: com.google.common.collect.Platform was unintentionally initialized at build time. io.fury.graalvm.Hello caused initialization of this class with the following trace:
at com.google.common.collect.Platform.<clinit>(Platform.java:35)
at com.google.common.collect.Sets.newConcurrentHashSet(Sets.java:275)
at io.fury.collection.MultiKeyWeakMap.<clinit>(MultiKeyWeakMap.java:43)
at io.fury.codegen.CodeGenerator.<clinit>(CodeGenerator.java:72)
at io.fury.codegen.Expression$Invoke.doGenCode(Expression.java:961)
at io.fury.codegen.Expression.genCode(Expression.java:103)
at io.fury.codegen.CodegenContext.addField(CodegenContext.java:515)
at io.fury.codegen.CodegenContext.addField(CodegenContext.java:500)
at io.fury.builder.BaseObjectCodecBuilder.<init>(BaseObjectCodecBuilder.java:150)
at io.fury.builder.ObjectCodecBuilder.<init>(ObjectCodecBuilder.java:87)
at io.fury.builder.CodecUtils.loadOrGenObjectCodecClass(CodecUtils.java:39)
at io.fury.serializer.CodegenSerializer.loadCodegenSerializer(CodegenSerializer.java:45)
at io.fury.resolver.ClassResolver.lambda$getObjectSerializerClass$2(ClassResolver.java:954)
at io.fury.resolver.ClassResolver$$Lambda$374/0x0000000931eec210.call(Unknown Source)
at io.fury.builder.JITContext.registerSerializerJITCallback(JITContext.java:132)
at io.fury.resolver.ClassResolver.getObjectSerializerClass(ClassResolver.java:952)
at io.fury.resolver.ClassResolver.getSerializerClass(ClassResolver.java:907)
at io.fury.resolver.ClassResolver.getSerializerClass(ClassResolver.java:810)
at io.fury.resolver.ClassResolver.createSerializer(ClassResolver.java:1168)
at io.fury.resolver.ClassResolver.getOrUpdateClassInfo(ClassResolver.java:1106)
at io.fury.resolver.ClassResolver.getSerializer(ClassResolver.java:781)
at io.fury.graalvm.Hello.<clinit>(Hello.java:14) |
make the package |
I get following error finally: com.oracle.svm.core.util.UserError$UserException: Classes that should be initialized at run time got initialized during image building:
io.fury.util.Platform was unintentionally initialized at build time. io.fury.graalvm.Hello caused initialization of this class with the following trace:
at io.fury.util.Platform.<clinit>(Platform.java:36)
at io.fury.util.ReflectionUtils.getFieldOffset(ReflectionUtils.java:310)
at io.fury.util.ReflectionUtils.getFieldOffset(ReflectionUtils.java:315)
at io.fury.serializer.StringSerializer.<clinit>(StringSerializer.java:77)
at io.fury.resolver.ClassResolver.addDefaultSerializers(ClassResolver.java:326)
at io.fury.resolver.ClassResolver.initialize(ClassResolver.java:319)
at io.fury.Fury.<init>(Fury.java:131)
at io.fury.config.FuryBuilder.newFury(FuryBuilder.java:317)
at io.fury.config.FuryBuilder.build(FuryBuilder.java:332)
at io.fury.graalvm.Hello.<clinit>(Hello.java:11)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.UserError.abort(UserError.java:73)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ProvenSafeClassInitializationSupport.checkDelayedInitialization(ProvenSafeClassInitializationSupport.java:277)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.classinitialization.ClassInitializationFeature.duringAnalysis(ClassInitializationFeature.java:164)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$10(NativeImageGenerator.java:770)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:86)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$11(NativeImageGenerator.java:770)
at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.AbstractAnalysisEngine.runAnalysis(AbstractAnalysisEngine.java:179)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:767)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:582)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:539)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:408)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:612)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.start(NativeImageGeneratorRunner.java:134)
at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:94)
------------------------------------------------------------------------------------------------------------------------ Added |
The field offset seems still different between graalvm build time and runtime. For example, for String class, the runtime offsets for fields are: value: 4
coder: 12
hash: 8
hashIsZero: 13 but build time offsets are: value: 20
coder: 16
hash: 12
hashIsZero: 17 @ennerf Do you know how to make the offset consistent between build time and runtime? If the offsets can't be consistent between processes, I must make the FURY codegen generate |
Sorry, I don't. I expected potential compression of fields due to the omission of unused fields, and I'm quite surprised to see that the fields are in a different order too. |
The different field order surprised me too. I'll generate Unsafe offset static constant to work around this. |
I've noticed two things {
"pattern":"\\Qfury/blacklist.txt\\E"
} |
@Ran-Mewo Sorry for our insufficiently detailed doc and vague error messages. AsyncCompilation is not supported for graalvm native image, all the fury compilation must happen and finished at graalvm build time. AsyncCompilation will delay it to the runtime, and load bytecode is not supported by graalvm native image. Fury should add a If you disabled |
Yeah seems like I do |
@Ran-Mewo Could you open a new issue for this? I can't reproduce it locally |
@chaokunyang It looks good to create a quarkus extension for Feel free to contact me or @gastaldi if you have any problem. |
Great, thanks for sharing this guideline. I will follow it and try to create a quarkus extension in next weeks |
FYI @chaokunyang I also create https://issues.apache.org/jira/browse/CAMEL-21396 to introduce a |
This is awesome! Looking forward to it. |
It will be also included in camel-spring-boot and later in camel-quarkus |
Hi @chaokunyang I create a propsal for |
Yeah, I'd like to add fury support for quarkus |
Perfect! I added you in the |
Is your feature request related to a problem? Please describe.
Java works but compile to native java doesn't work.
Native Java:
https://www.graalvm.org/
Describe the solution you'd like
Add native java support by adding classes that need reflection to meta files.
Additional context
Exception when run in native java.
java.lang.RuntimeException: java.lang.UnsupportedOperationException: java.lang.NoSuchMethodException: java.util.concurrent.ConcurrentSkipListSet.(java.util.Comparator)
at com.aigens.api.TestApi.hello(TestApi.java:85)
at com.aigens.api.TestApi$quarkusrestinvoker$hello_a988a40893ff99606bf40d6c00ea871031f3749c.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at [email protected]/java.lang.Thread.run(Thread.java:833)
at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775)
at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203)
Caused by: java.lang.UnsupportedOperationException: java.lang.NoSuchMethodException: java.util.concurrent.ConcurrentSkipListSet.(java.util.Comparator)
at io.fury.serializer.CollectionSerializers$SortedSetSerializer.(CollectionSerializers.java:521)
at io.fury.serializer.CollectionSerializers$ConcurrentSkipListSetSerializer.(CollectionSerializers.java:707)
at io.fury.serializer.CollectionSerializers.registerDefaultSerializers(CollectionSerializers.java:918)
at io.fury.resolver.ClassResolver.addDefaultSerializers(ClassResolver.java:309)
at io.fury.resolver.ClassResolver.initialize(ClassResolver.java:297)
at io.fury.Fury.(Fury.java:128)
The text was updated successfully, but these errors were encountered: