Quarkus: @RegisterForReflection cannot be added to --initialize-at-run-time=

Created on 11 Feb 2020  路  20Comments  路  Source: quarkusio/quarkus

Describe the bug
Classes marked with @RegisterForReflection cannot be added to --initialize-at-run-time= because Quarkus attempts to initialize the classes at io.quarkus.runner.AutoFeature.beforeAnalysis(AutoFeature.zig:970) during a native image build.

Expected behavior
I can annotate a class with @RegisterForReflection and also --initialize-at-run-time=.

Actual behavior
In this case, myClass is generated with the avro maven plugin. I have customized the velocity template to annotate the class with @RegisterForReflection so I do not have to write out all the classes (there are a lot) in a reflection-config.json. That said, reflection-config.json seems to work just fine.

com.oracle.svm.core.util.UserError$UserException: Classes that should be initialized at run time got initialized during image building:
 myClass the class was requested to be initialized at build time (from the command line). myClass caused initialization of this class with the following trace: 
        at io.quarkus.runner.AutoFeature.beforeAnalysis(AutoFeature.zig:970)
        at com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$7(NativeImageGenerator.java:670)
        at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:63)
        at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:670)
        at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:526)
        at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:444)
        at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
        at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
        at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
        at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
        at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

        at com.oracle.svm.core.util.UserError.abort(UserError.java:65)
        at com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.checkDelayedInitialization(ConfigurableClassInitialization.java:494)
        at com.oracle.svm.hosted.classinitialization.ClassInitializationFeature.duringAnalysis(ClassInitializationFeature.java:188)
        at com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$8(NativeImageGenerator.java:711)
        at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:63)
        at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:711)
        at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:526)
        at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:444)
        at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
        at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
        at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
        at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
        at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

To Reproduce
Steps to reproduce the behavior:

  1. Create a class with @RegisterForReflection and add it to --initialize-at-run-time=
  2. mvn clean install -Dnative -Dquarkus.native.container-build=true
  3. See the stack track

Configuration

quarkus.native.additional-build-args=--initialize-at-run-time=my.package.avro,-H:+TraceClassInitialization


Environment (please complete the following information):

  • Output of uname -a or ver: Darwin Kernel Version 18.7.0: Sun Dec 1 18:59:03 PST 2019; root:xnu-4903.278.19~1/RELEASE_X86_64 x86_64
  • Output of java -version: openjdk version "1.8.0_212"
    OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03)
    OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)
  • GraalVM version (if different from Java): 19.2.1-grl via sdkman
  • Quarkus version or git rev: 1.1.1.Final

Additional context
It may be worth considering an avro extension to handle initialization and reflection for classes generated from avro schema.

As a work around, I've been able to further customize the velocity template for the avro generated code to move all the static initializers to the default constructor, which will incur the cost on the first time the class is instantiated. Ideally, this would not be required and we could do this work in a quarkus extension.

kinbug triagout-of-date

Most helpful comment

FYI, the problem was that reflection registration in quarkus was loading the class and initializing it at static-init. Fix was just to not initialize the class. My PR should fix it.

All 20 comments

Hi @sherl0cks.

The @RegisterForReflection annotation was specifically designed to initialize a class at build time during the native image generation so that GraalVM doesn't remove it because the class is not used directly in the call tree.

That's why you can't use @RegisterForReflection and --initialize-at-run-time for the same class.

@gwenneg OK. Why does reflection-config.json work for the scenario? Are they not equivalent features?

reflection-config.json is a GraalVM feature while @RegisterForReflection is a Quarkus feature, they are not strictly equivalent. I'll take a look at the GraalVM internal logic related to reflection-config.json and try to understand why there's a behavior difference between these two features.

@gwenneg that makes sense, thank you. Can you confirm that if I add a class to --initialize-at-run-time that it will prevent graal from removing methods? And thus, if there are classes that need reflection at runtime, but for whatever reason (e.g. logic from are avro code generation), cannot be initialized at build time, that it is sufficient to add these classes to --initialize-at-run-time and they will be available for reflection?

I never tried it but I don't think using --initialize-at-runtime will protect a class from removal. As far as I know, reflection-config.json and @RegisterForReflection are the only available solutions for that.

@gwenneg ok, that is useful info. I'm running some tests now and will report back.

Funnily enough, there is an ongoing thread on the list: https://groups.google.com/forum/#!topic/quarkus-dev/VDJ6_Slc0yU . In the end, it might indeed be a bug or at least an undesired behavior.

Funny that Bill brought that up, as he is working on #7165 and #6785 which also came my team. Also looks like a stackoverflow thread on it too: https://stackoverflow.com/questions/59413224/quarkus-native-build-class-in-both-build-and-run-time-initialization. So clearly this is not a one off issue.

As to my testing. I can confirm that when only specifying --initialize-at-runtime without reflection-config.json or @RegisterForReflection, graal will remove the class at build time and it is not available in the native image at runtime =/. So it does seem that getting to the bottom of this issue with @RegisterForReflection will provide value to users. It's a great extension to graal - these sorts of developer experience things are what makes quarkus great.

For anyone that finds this issue and is trying to fix the problem with avro generated classes, here is my current work around:

  1. set up the maven plugin to use custom velocity templates with <templateDirectory/>
  2. copy over the templates from https://github.com/apache/avro/tree/master/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic
  3. modify the record template to 1) annotate with @RegisterForReflection and 2) initialize the static members in the constructor if not null, not statically. See https://gist.github.com/sherl0cks/3c6228f2080579c1c2961af51576a9b6

I think there's a good chance @patriot1burke fixed it here: https://github.com/quarkusio/quarkus/pull/7165/files#diff-f0cb01672f1c4cc99791becacd492916R260 .

FYI, the problem was that reflection registration in quarkus was loading the class and initializing it at static-init. Fix was just to not initialize the class. My PR should fix it.

@patriot1burke well that was a lucky bit of serendipity right there. Really appreciate your collaboration on these issues.

@sherl0cks Did you have the occasion to test Quarkus 1.3.0.Alpha2? This version includes @patriot1burke's commit.

@gwenneg no. Thanks for letting me know. It's been a really busy few days but will try to squeeze it in.

@sherl0cks ping

Thanks for the reminder @machi1990. I got my code back to a place where I can reproduce this bug on 1.1.1.Final, but I cannot resolve the quarkus-universe-bom for 1.3.0.Alpha2 to test the new release. Looked around all over for it but to no avail. My build is set up around that bom and I dont have the cycles to sort out the dependency tree otherwise.

Where can I find that bom? I have the branch saved and can test quickly once I have it.

Also tried switching to quarkus-bom but then my build fails with the below, which feels like a dependency issue:

ervice: Failed to build a runnable JAR: Failed to augment application classes: Build failure: Build failed due to errors
[ERROR]         [error]: Build step io.quarkus.deployment.steps.BootstrapConfigSetupBuildStep#setupBootstrapConfig threw an exception: java.lang.NoSuchFieldError: C_CREATE_BOOTSTRAP_CONFIG

@machi1990 @gwenneg any info on how I can resolve the artifacts so I can test?

Also tried switching to quarkus-bom but then my build fails with the below, which feels like a dependency issue:

ervice: Failed to build a runnable JAR: Failed to augment application classes: Build failure: Build failed due to errors
[ERROR]         [error]: Build step io.quarkus.deployment.steps.BootstrapConfigSetupBuildStep#setupBootstrapConfig threw an exception: java.lang.NoSuchFieldError: C_CREATE_BOOTSTRAP_CONFIG

Hey sorry for the late reply, updating to quarkus-bom should be enough. Did you update the maven plugin to 1.3.0.Alpha2 too?

That was it. I can confirm that the build issue is resolved with 1.3.0.Alpha2!

I am unable to deploy and test this app right now for other reasons, but given the patch and the nature of the issue, I think we are good to close this.

@sherl0cks thanks for the update and I am glad the issue is resolved with the said patch.

Was this page helpful?
0 / 5 - 0 ratings