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

[Java] Support GraalVM Native Image #678

Closed
aigens opened this issue Jul 17, 2023 · 45 comments · Fixed by #1168
Closed

[Java] Support GraalVM Native Image #678

aigens opened this issue Jul 17, 2023 · 45 comments · Fixed by #1168
Labels
enhancement New feature or request java

Comments

@aigens
Copy link

aigens commented Jul 17, 2023

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)

@aigens aigens added the enhancement New feature or request label Jul 17, 2023
@chaokunyang
Copy link
Collaborator

@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?

@aigens
Copy link
Author

aigens commented Jul 18, 2023

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:
https://quarkus.io

Looking forward to Fury!

@chaokunyang chaokunyang changed the title Support GraalVM Native Java Build [Java] Support GraalVM Native Java Build Jul 18, 2023
@chaokunyang
Copy link
Collaborator

chaokunyang commented Jul 20, 2023

@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.

@ennerf
Copy link

ennerf commented Aug 2, 2023

@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

@geoand
Copy link

geoand commented Aug 2, 2023

Disclaimer: I am a member of the Quarkus team

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?

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.
The Quarkus specific way would be to have a Quarkus extension that simply integrates with Fury in order to generate the serializes at build time. Obviously this would require the library to be modular that there exist some SPIs that would be used to determine the classes in need of serialization and a backend part of how to actually write the new bytecode.
An alternative would be to have an annotation processor which has the advantage of being framework agnostic, but also carries the downside of needing a different solution for Kotlin source code.

@chaokunyang
Copy link
Collaborator

Hi @ennerf @geoand thanks for your inputs, it's very valuable.

If I understand right, there are two ways to support native image:

  1. Fury provide a group of annotation and a annotation processor. Then users use annotation to mark their class. The annotation processor will invoke fury jit api to generate Java code. And generate graalvm refection config for those classes and generated serializer. In this way, those class will not be cropped by graal. And Fury can load those classes at runtime.
  2. Fury provide the Java code/bytecode generation api to Quarku. And Fury define a spi to let the users list all classes in need of serialization. Then Quarkus invoke fury at build time to generate serializer code and register instanse of generated serializer into fury?

@chaokunyang
Copy link
Collaborator

@geoand Fury supports generate Java code or bytecode. If we generate Java code, will it affect kotlin build?

@geoand
Copy link

geoand commented Aug 2, 2023

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.

@chaokunyang
Copy link
Collaborator

chaokunyang commented Aug 2, 2023

@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.

@geoand
Copy link

geoand commented Aug 2, 2023

👍🏼

@chaokunyang
Copy link
Collaborator

chaokunyang commented Aug 3, 2023

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.

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.

@Ran-Mewo
Copy link

You can manually generate all the reflection configurations if you first run everything with the graal agent and then compile against that
The problem I am facing is that fury tries to create hidden classes, which are not possible at run time
Initializing fury at build time also doesn't work, as it then tries to do file operations, which are not possible at build time

@chaokunyang
Copy link
Collaborator

create hidden classes,

@Ran-Mewo Fury uses MethodHandle to generate lambda to reduce reflection cost for method getter and constructor, is this the place where hidden classes are created? If so, we can fallback to reflection/unsafe for this.

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.

@Ran-Mewo
Copy link

I suppose so? Due to me seeing the class io.fury.util.unsafe._JDKAccess.makeGetterFunction. And I am not sure wether if graal would allow generating code at build time, but if I had to take a guess, then I think it'd probably allow generating Java code at build time, which the compiler will compile down to native, but everything is locked up at run time and no more new classes are expected to be generated.

Have both of the stacktraces for run time and build time

Initializing at run time:

java.lang.InternalError: com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
	at [email protected]/java.lang.invoke.InnerClassLambdaMetafactory.generateInnerClass(InnerClassLambdaMetafactory.java:500)
	at [email protected]/java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:402)
	at [email protected]/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:315)
	at [email protected]/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:341)
	at io.fury.util.unsafe._JDKAccess.makeGetterFunction(_JDKAccess.java:283)
	at io.fury.util.function.Functions.makeGetterFunction(Functions.java:84)
	at io.fury.util.function.Functions.makeGetterFunction(Functions.java:71)
	at io.fury.serializer.Serializers.getBuilderFunc(Serializers.java:179)
	at io.fury.serializer.Serializers.access$000(Serializers.java:58)
	at io.fury.serializer.Serializers$AbstractStringBuilderSerializer.<init>(Serializers.java:200)
	at io.fury.serializer.Serializers$StringBuilderSerializer.<init>(Serializers.java:246)
	at io.fury.serializer.Serializers.registerDefaultSerializers(Serializers.java:557)
	at io.fury.resolver.ClassResolver.addDefaultSerializers(ClassResolver.java:324)
	at io.fury.resolver.ClassResolver.initialize(ClassResolver.java:314)
	at io.fury.Fury.<init>(Fury.java:131)
	at io.fury.config.FuryBuilder.newFury(FuryBuilder.java:297)
	at io.fury.config.FuryBuilder.lambda$buildThreadLocalFury$0(FuryBuilder.java:327)
	at io.fury.util.LoaderBinding.setClassLoader(LoaderBinding.java:93)
	at io.fury.util.LoaderBinding.setClassLoader(LoaderBinding.java:65)
	at io.fury.ThreadLocalFury.lambda$new$1(ThreadLocalFury.java:47)
	at [email protected]/java.lang.ThreadLocal$SuppliedThreadLocal.initialValue(ThreadLocal.java:305)
	at [email protected]/java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:195)
	at [email protected]/java.lang.ThreadLocal.get(ThreadLocal.java:172)
	at io.fury.ThreadLocalFury.<init>(ThreadLocalFury.java:53)
	at io.fury.config.FuryBuilder.buildThreadLocalFury(FuryBuilder.java:327)
	at io.fury.config.FuryBuilder.buildThreadSafeFury(FuryBuilder.java:317)
	at me.ran.rumi.serializers.Serializers.<clinit>(Serializers.java:26)

Initializing at build time

Error: No instances of java.io.FilePermission are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized through the following trace:
	at java.io.FilePermission.<init>(FilePermission.java:489)
No instances of java.io.FilePermission are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized through the following trace:

	at sun.net.www.protocol.file.FileURLConnection.getPermission(FileURLConnection.java:234)
	at java.net.URLClassLoader.getPermissions(URLClassLoader.java:725)
	at java.security.SecureClassLoader$1.apply(SecureClassLoader.java:226)
	at java.security.SecureClassLoader$1.apply(SecureClassLoader.java:222)
	at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1740)
	at java.security.SecureClassLoader.getProtectionDomain(SecureClassLoader.java:222)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:427)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:421)
	at java.security.AccessController.executePrivileged(AccessController.java:807)
	at java.security.AccessController.doPrivileged(AccessController.java:712)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:420)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:587)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at jdk.internal.loader.Loader.loadClass(Loader.java:564)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	at java.lang.Class.forName0(Unknown Source)
	at java.lang.Class.forName(Class.java:467)
	at com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:307)
	at com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:303)
	at com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:312)
	at com.oracle.svm.hosted.NativeImageClassLoaderSupport$LoadClassHandler.handleClassFileName(NativeImageClassLoaderSupport.java:784)
	at com.oracle.svm.hosted.NativeImageClassLoaderSupport$LoadClassHandler$1.lambda$visitFile$0(NativeImageClassLoaderSupport.java:713)
	at com.oracle.svm.hosted.NativeImageClassLoaderSupport$LoadClassHandler$1$$Lambda$280/0x00000007c0451718.run(Unknown Source)
	at com.oracle.svm.hosted.ImageClassLoader$1.lambda$execute$0(ImageClassLoader.java:94)
	at com.oracle.svm.hosted.ImageClassLoader$1$$Lambda$254/0x00000007c02cfc78.run(Unknown Source)
	at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
.  To fix the issue mark java.io.FilePermission for build-time initialization with --initialize-at-build-time=java.io.FilePermission or use the the information from the trace to find the culprit and --initialize-at-run-time=<culprit> to prevent its instantiation.

Detailed message:
Trace: Object was reached by
  reading field java.util.concurrent.ConcurrentHashMap$Node.val of constant 
    java.util.concurrent.ConcurrentHashMap$Node@6abb07c: I:\packages\gradle\caches\modules-2\files-2.1\com.google.guava\guava\32.1.2-jre\...
  indexing into array java.util.concurrent.ConcurrentHashMap$Node[]@3715bd62: [Ljava.util.concurrent.ConcurrentHashMap$Node;@3715bd62
  reading field java.util.concurrent.ConcurrentHashMap.table of constant 
    java.util.concurrent.ConcurrentHashMap@7e665f2: {I:\packages\gradle\caches\modules-2\files-2.1\com.google.guava\guava\32.1.2-jre...
  reading field java.io.FilePermissionCollection.perms of constant 
    java.io.FilePermissionCollection@512c1a76: java.io.FilePermissionCollection@512c1a76 (
 ("java.io.FilePermission" "I:\packa...
  reading field java.util.concurrent.ConcurrentHashMap$Node.val of constant 
    java.util.concurrent.ConcurrentHashMap$Node@7728c4f8: class java.io.FilePermission=java.io.FilePermissionCollection@512c1a76 (
 ("java...
  indexing into array java.util.concurrent.ConcurrentHashMap$Node[]@75d8a377: [Ljava.util.concurrent.ConcurrentHashMap$Node;@75d8a377
  reading field java.util.concurrent.ConcurrentHashMap.table of constant 
    java.util.concurrent.ConcurrentHashMap@63d6a948: {class java.io.FilePermission=java.io.FilePermissionCollection@512c1a76 (
 ("jav...
  reading field java.security.Permissions.permsMap of constant 
    java.security.Permissions@37f22a09: java.security.Permissions@37f22a09 (
 ("java.io.FilePermission" "I:\packages\gra...
  reading field java.security.ProtectionDomain.permissions of constant 
    java.security.ProtectionDomain@10178780: ProtectionDomain  (file:/I:/packages/gradle/caches/modules-2/files-2.1/com.googl...
  reading field java.lang.invoke.MethodHandles$Lookup.cachedProtectionDomain of constant 
    java.lang.invoke.MethodHandles$Lookup@25de25ca: /trusted
  reading field java.util.concurrent.ConcurrentHashMap$Node.val of constant 
    java.util.concurrent.ConcurrentHashMap$Node@157b751e: class com.google.common.collect.ImmutableMap$Builder=/trusted
  reading field java.util.concurrent.ConcurrentHashMap$Node.next of constant 
    java.util.concurrent.ConcurrentHashMap$Node@5c07a258: interface java.util.List=/trusted
  indexing into array java.util.concurrent.ConcurrentHashMap$Node[]@d0642f2: [Ljava.util.concurrent.ConcurrentHashMap$Node;@d0642f2
  reading field java.util.concurrent.ConcurrentHashMap.table of constant 
    java.util.concurrent.ConcurrentHashMap@51100a93: {class java.util.ImmutableCollections$Map1=/trusted, class com.google.common.col...
  reading field java.lang.ClassValue.values of constant 
    io.fury.util.unsafe._JDKAccess$1@1d2bbf7d: io.fury.util.unsafe._JDKAccess$1@1d2bbf7d
  scanning root io.fury.util.unsafe._JDKAccess$1@1d2bbf7d: io.fury.util.unsafe._JDKAccess$1@1d2bbf7d embedded in 
    io.fury.util.unsafe._JDKAccess._trustedLookup(_JDKAccess.java:105)
  parsing method io.fury.util.unsafe._JDKAccess._trustedLookup(_JDKAccess.java:105) reachable via the parsing context
    at io.fury.util.ReflectionUtils.getCtrHandle(ReflectionUtils.java:127)
    at io.fury.serializer.MapSerializers$SortedMapSerializer.<init>(MapSerializers.java:849)
    at com.oracle.svm.core.code.FactoryMethodHolder.MapSerializers$ConcurrentSkipListMapSerializer_constructor_95ea8caa5afeea9f9ba7929729219556d4305e3b(generated:0)
    at io.fury.serializer.MapSerializers.registerDefaultSerializers(MapSerializers.java:1109)
    at io.fury.resolver.ClassResolver.addDefaultSerializers(ClassResolver.java:328)
    at io.fury.resolver.ClassResolver.initialize(ClassResolver.java:314)
    at io.fury.Fury.<init>(Fury.java:131)
    at com.oracle.svm.core.code.FactoryMethodHolder.Fury_constructor_8d9383daf664f347d2d9fc4d58d7bb75b66b39f7(generated:0)
    at io.fury.config.FuryBuilder.newFury(FuryBuilder.java:297)
    at io.fury.config.FuryBuilder$$Lambda$325/0x00000007c1997258.apply(Unknown Source)
    at io.fury.util.LoaderBinding.setClassLoader(LoaderBinding.java:104)
    at io.fury.ThreadLocalFury.lambda$new$1(ThreadLocalFury.java:47)
    at io.fury.ThreadLocalFury$$Lambda$327/0x00000007c19976b0.get(Unknown Source)
    at java.lang.ThreadLocal$SuppliedThreadLocal.initialValue(ThreadLocal.java:305)
    at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:195)
    at java.lang.ThreadLocal.get(ThreadLocal.java:172)
    at jdk.internal.math.FloatingDecimal.getBinaryToASCIIBuffer(FloatingDecimal.java:986)
    at jdk.internal.math.FloatingDecimal.getBinaryToASCIIConverter(FloatingDecimal.java:1782)
    at jdk.internal.math.FloatingDecimal.toJavaFormatString(FloatingDecimal.java:70)
    at java.lang.Double.toString(Double.java:769)
    at root method.(Unknown Source)

(Also yes I know it said to do --initialize-at-build-time=java.io.FilePermission but doing so gives another error where it basically says that initializing this class is impossible due to it breaking JNI access)

@chaokunyang
Copy link
Collaborator

@Ran-Mewo For first exception Defining hidden classes at runtime is not supported, maybe we can update io.fury.util.function.Functions.makeGetterFunction to catch up error and return an implementation based on Unsafe/Reflection.

@Ran-Mewo
Copy link

@Ran-Mewo For first exception Defining hidden classes at runtime is not supported, maybe we can update io.fury.util.function.Functions.makeGetterFunction to catch up error and return an implementation based on Unsafe/Reflection.

Well at this point we'll just have to try it and see

@chaokunyang
Copy link
Collaborator

@Ran-Mewo I'll try it tomorrow and give the feedbacks to you.

@ennerf
Copy link

ennerf commented Nov 18, 2023

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.

@geoand
Copy link

geoand commented Nov 18, 2023

Your best bet is probably to generate the classes at compile time, which seems to be what everyone else is doing

+1

@chaokunyang
Copy link
Collaborator

chaokunyang commented Nov 19, 2023

I read the main graalvm doc. One of the last thing remained is Unsafe offset:
image
https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/#unsafe-accesses

image
https://www.graalvm.org/latest/reference-manual/native-image/metadata/Compatibility/#unsafe-memory-access

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.

@chaokunyang
Copy link
Collaborator

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.

@chaokunyang
Copy link
Collaborator

Your best bet is probably to generate the classes at compile time, which seems to be what everyone else is doing.

@ennerf You mean generate the classes at graal image build time? Or generate classes ahead before pass to graal for compilation.

@chaokunyang
Copy link
Collaborator

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)

@Ran-Mewo
Copy link

make the package com.google.common.collect also be intiailized at build time

@chaokunyang
Copy link
Collaborator

make the package com.google.common.collect also be intiailized at build time

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 sun.misc.Unsafe,io.fury.util.unsafe._JDKAccess to build time init doesn't work

@chaokunyang
Copy link
Collaborator

chaokunyang commented Nov 25, 2023

@ennerf You mean generate the classes at graal image build time? Or generate classes ahead before pass to graal for compilation.

generate classes beforehand via e.g. an annotation processor

The field offset issue seems to apply only if some fields are not used and get eliminated. If you use an annotation processor you can additionally generate an exclusion that specifies to keep all fields of that class (although serialization should already use everything anyways), and add an assertion at initialization that things behave correctly.

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 VarHandle/ Static offset constants instead, and make the Fury source code support UnsafeAutomaticSubstitutionProcessor too, which won't be easy.

@chaokunyang
Copy link
Collaborator

chaokunyang commented Nov 25, 2023

@ennerf @Ran-Mewo @geoand I managed make Fury generate code at build time in #1143, if I fixed the Unsafe offset, I think the Fury graalvm support will come soon.

@ennerf
Copy link

ennerf commented Nov 25, 2023

@ennerf Do you know how to make the offset consistent between build time and runtime?

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.

@chaokunyang
Copy link
Collaborator

@ennerf Do you know how to make the offset consistent between build time and runtime?

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.

@Ran-Mewo
Copy link

Ran-Mewo commented Dec 3, 2023

I've noticed two things
.withAsyncCompilation(true) doesn't work and the error message was very vague, took me hours to realize this was causing the issue
and also for me I need to add this to my resource config

{
    "pattern":"\\Qfury/blacklist.txt\\E"
}

@chaokunyang
Copy link
Collaborator

I've noticed two things .withAsyncCompilation(true) doesn't work and the error message was very vague, took me hours to realize this was causing the issue and also for me I need to add this to my resource config

@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 AsyncCompilation=falsecheck for graalvm build by check it in FuryBuilder#finish. Or just set it to false and issue an warning for it, so the config will take effect for other JDK without change your serialization code.

If you disabled AsyncCompilation, do you still need to add blacklist.txt?

@Ran-Mewo
Copy link

Ran-Mewo commented Dec 4, 2023

If you disabled AsyncCompilation, do you still need to add blacklist.txt?

Yeah seems like I do

@chaokunyang
Copy link
Collaborator

chaokunyang commented Dec 5, 2023

If you disabled AsyncCompilation, do you still need to add blacklist.txt?

Yeah seems like I do

@Ran-Mewo Could you open a new issue for this? I can't reproduce it locally

@zhfeng
Copy link
Contributor

zhfeng commented Jul 26, 2024

@chaokunyang It looks good to create a quarkus extension for fury and please check Getting an extension onboarded.

Feel free to contact me or @gastaldi if you have any problem.

@chaokunyang
Copy link
Collaborator

chaokunyang commented Jul 27, 2024

@chaokunyang It looks good to create a quarkus extension for fury and please check Getting an extension onboarded.

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

@zhfeng
Copy link
Contributor

zhfeng commented Oct 29, 2024

FYI @chaokunyang I also create https://issues.apache.org/jira/browse/CAMEL-21396 to introduce a camel-fury component to get fury to integrate with the apache camel framework.

@chaokunyang
Copy link
Collaborator

FYI @chaokunyang I also create https://issues.apache.org/jira/browse/CAMEL-21396 to introduce a camel-fury component to get fury to integrate with the apache camel framework.

This is awesome! Looking forward to it.

@zhfeng
Copy link
Contributor

zhfeng commented Oct 29, 2024

It will be also included in camel-spring-boot and later in camel-quarkus

@zhfeng
Copy link
Contributor

zhfeng commented Oct 31, 2024

Hi @chaokunyang

I create a propsal for quarkus-fury extension and do you mind to lead this project?

@chaokunyang
Copy link
Collaborator

Hi @chaokunyang

I create a propsal for quarkus-fury extension and do you mind to lead this project?

Yeah, I'd like to add fury support for quarkus

@zhfeng
Copy link
Contributor

zhfeng commented Oct 31, 2024

Perfect! I added you in the Team Members in the proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request java
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants