apache / fury

A blazingly fast multi-language serialization framework powered by JIT and zero-copy.
https://fury.apache.org/
Apache License 2.0
3.06k stars 239 forks source link

[Java] Support GraalVM Native Image #678

Closed aigens closed 10 months ago

aigens commented 1 year ago

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 java.base@17.0.5/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)

chaokunyang commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

geoand commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

👍🏼

chaokunyang commented 1 year ago

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 commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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 java.base@17.0.9/java.lang.invoke.InnerClassLambdaMetafactory.generateInnerClass(InnerClassLambdaMetafactory.java:500)
    at java.base@17.0.9/java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:402)
    at java.base@17.0.9/java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:315)
    at java.base@17.0.9/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 java.base@17.0.9/java.lang.ThreadLocal$SuppliedThreadLocal.initialValue(ThreadLocal.java:305)
    at java.base@17.0.9/java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:195)
    at java.base@17.0.9/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 commented 11 months ago

@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 commented 11 months ago

@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 commented 11 months ago

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

ennerf commented 11 months ago

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 commented 11 months ago

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

+1

chaokunyang commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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 commented 11 months ago

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

chaokunyang commented 11 months ago

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 commented 11 months ago

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.

Found a blog for Unsafe offset in graalvm native image: https://developers.redhat.com/articles/2022/05/09/using-unsafe-safely-graalvm-native-image .

ennerf commented 11 months ago

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

chaokunyang commented 11 months ago

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

@ennerf I see, thanks, generate code to capture all fields and assert is a good suggestion, I'll give it a try. About generating code at build time or compile time, I will do more experimentations. The compile time codegen is more predicable but doesn't work for thirdparty libs objects.

chaokunyang commented 11 months ago

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.(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:

@Ran-Mewo For this error, Fury can be updated to don't hold strong reference as static variable too address this error. Currently most reference comes from ClassValue cache in fury. We can replace it by a no cache implementation for graalvm

chaokunyang commented 11 months ago

@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 commented 11 months ago

@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 commented 11 months ago

@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 commented 10 months ago

@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 commented 10 months ago

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 commented 10 months ago

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 commented 10 months ago

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

Yeah seems like I do

chaokunyang commented 10 months ago

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 commented 2 months ago

@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 commented 2 months ago

@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