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

AWT extension fails native build because "Classes that should be initialized at run time got initialized during image building" #30908

Closed
ksilz opened this issue Feb 6, 2023 · 17 comments
Assignees

Comments

@ksilz
Copy link

ksilz commented Feb 6, 2023

Describe the bug

I wrote a program that creates Thumbnails for images. It uses the Thumbnailator library. For native execution, Quarkus needs the AWT extension - it through a Add AWT Quarkus extension to enable Java2D/ImageIO without it. But building a native image with the AWT extension fails.

Expected behavior

./mvnw clean install -Dnative in my project completes.

Actual behavior

./mvnw clean install -Dnative in my project completes fails:

[2/7] Performing analysis...  [*]                                                                        (9.4s @ 2.01GB)
   6,107 (81.40%) of  7,502 classes reachable
   9,736 (62.35%) of 15,614 fields reachable
  25,346 (45.04%) of 56,274 methods reachable
     223 classes,     1 fields, and 1,371 methods registered for reflection
       1 native library: -framework CoreServices

Error: Classes that should be initialized at run time got initialized during image building:
 sun.awt.SunHints the class was requested to be initialized at run time (from feature io.quarkus.awt.runtime.graal.AwtFeature.afterRegistration with 'sun.awt'). To see why sun.awt.SunHints got initialized use --trace-class-initialization=sun.awt.SunHints
java.awt.RenderingHints the class was requested to be initialized at run time (from feature io.quarkus.awt.runtime.graal.AwtFeature.afterRegistration with 'java.awt'). To see why java.awt.RenderingHints got initialized use --trace-class-initialization=java.awt.RenderingHints
To see how the classes got initialized, use --trace-class-initialization=sun.awt.SunHints,java.awt.RenderingHints
com.oracle.svm.core.util.UserError$UserException: Classes that should be initialized at run time got initialized during image building:
 sun.awt.SunHints the class was requested to be initialized at run time (from feature io.quarkus.awt.runtime.graal.AwtFeature.afterRegistration with 'sun.awt'). To see why sun.awt.SunHints got initialized use --trace-class-initialization=sun.awt.SunHints
java.awt.RenderingHints the class was requested to be initialized at run time (from feature io.quarkus.awt.runtime.graal.AwtFeature.afterRegistration with 'java.awt'). To see why java.awt.RenderingHints got initialized use --trace-class-initialization=java.awt.RenderingHints
To see how the classes got initialized, use --trace-class-initialization=sun.awt.SunHints,java.awt.RenderingHints
	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:273)
	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:748)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:85)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$11(NativeImageGenerator.java:748)
	at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.AbstractAnalysisEngine.runAnalysis(AbstractAnalysisEngine.java:162)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:745)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:578)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:535)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:403)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:580)
	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:128)
------------------------------------------------------------------------------------------------------------------------
                        0.4s (2.7% of total time) in 16 GCs | Peak RSS: 2.40GB | CPU load: 4.09
========================================================================================================================
Failed generating 'bug-quarkus-awt-extension-1.0-runner' after 15.5s.
Error: Image build request failed with exit status 1

How to Reproduce?

  1. Clone my repo.
  2. Run ./mvnw clean install -Dnative.

Output of uname -a or ver

Darwin m1x-773.local 22.3.0 Darwin Kernel Version 22.3.0: Thu Jan 5 20:48:54 PST 2023; root:xnu-8792.81.2~2/RELEASE_ARM64_T6000 arm64

Output of java -version

openjdk version "17.0.5" 2022-10-18 OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08) OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.16.2.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63) Maven home: /Users/karsten/.m2/wrapper/dists/apache-maven-3.8.6-bin/67568434/apache-maven-3.8.6 Java version: 17.0.5, vendor: GraalVM Community, runtime: /Users/karsten/.sdkman/candidates/java/22.3.r17-grl Default locale: en_US, platform encoding: UTF-8 OS name: "mac os x", version: "13.2", arch: "aarch64", family: "mac"

Additional information

No response

@ksilz ksilz added the kind/bug Something isn't working label Feb 6, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Feb 6, 2023

/cc @Karm (awt), @galderz (awt), @zakkak (awt)

@geoand geoand changed the title [BUG] AWT extension fails native build because "Classes that should be initialized at run time got initialized during image building" AWT extension fails native build because "Classes that should be initialized at run time got initialized during image building" Feb 6, 2023
@Karm Karm self-assigned this Feb 6, 2023
@ksilz
Copy link
Author

ksilz commented Feb 6, 2023

@geoand This isn't a critical issue for my demo at LJC Live next week. As it turns out, AWT doesn't work right now on macOS (it may have never). It's on the GraalVM roadmap.

@geoand
Copy link
Contributor

geoand commented Feb 6, 2023

Good to know, thanks for the update.

@ksilz
Copy link
Author

ksilz commented Feb 6, 2023

I switched my demo app from creating thumbnails for pictures to converting pictures to PDF. That ain't using AWT. 😉

@geoand
Copy link
Contributor

geoand commented Feb 6, 2023

💪🏼

@ksilz
Copy link
Author

ksilz commented Feb 6, 2023

I changed my app now so the error won't show anymore. I'll provide new recreation steps tomorrow morning.

@ksilz
Copy link
Author

ksilz commented Feb 12, 2023

@geoand @Karm I finally created a repo that demonstrates this bug. I updated the description accordingly. The application works from the IDE and when running as an uber JAR but fails the native build.

The error doesn't show with light use of the AWT extension - say, instantiating an Image. For me, it only shows when using the Thumbnailator library.

@Karm
Copy link
Member

Karm commented Feb 13, 2023

@ksilz thanks for the reproducer. I will take a look presently. (using my CentOS 8 Stream deck)

@Karm
Copy link
Member

Karm commented Feb 15, 2023

Hello @ksilz,

I had taken a look at the reproducer and I opened a PR fixing the project, see: ksilz/bug-quarkus-awt-extension#1

See the successful log on my machine (CentOS 8 Stream, amd64):
https://karms.biz/pastebin/quarkus-issues-30908-good-run.txt

This is not a bug, I would say. It's rather a fact of interacting with JDK's graphics libraries and native compilation.

JDK's graphics libraries hold state after init, they are interwoven with JNI code that holds its own state too and we cannot have any of that at build time. We need to defer many related JDK classes to runtime init rather than build time init, we also need to allow for refclection at runtime and we need to set JNI access at runtime (e.g. a code in C decides to call some Java code via JNI).

AWT extension does that heavy lifting for you in Quarkus, see mostly this code: https://github.com/quarkusio/quarkus/blob/main/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java

It allows you create thumbnails or watermark images without any additional hassle, see e.g. this QuickStart:
https://github.com/quarkusio/quarkus-quickstarts/tree/main/awt-graphics-rest-quickstart

When you let net.coobird.thumbnailator in as a dependency, you opened the door for Thumbnailator trying to init some of those classes at build time, we definitely need to be deferred to runtime.

What I did for you in the aforementioned PR is that I told the native-image compiler via the Quarkus option to defer those classes' init to runtime too.

It will work just fine until something in Thumbnailator changes that could affect this area again.

I will close this issue as it is not a Quarkus AWT extension bug. Feel free to reach out on Zulip or here again to discuss more though.

@Karm Karm closed this as completed Feb 15, 2023
@Karm
Copy link
Member

Karm commented Feb 15, 2023

...if you need that in many Quarkus apps and maintaining that list of classes becomes tedious, a tiny Thumbnailator Quarkus Extension might be in order. You can then use it as a dependency in your projects.

@ksilz
Copy link
Author

ksilz commented Feb 15, 2023

@Karm Thank you for your feedback! Some questions.

  1. This is not a general GraalVM issue, as my Spring Boot version with the very same code builds fine to native. This is a Quarkus-only issue, as it defaults to build-time initialization. Correct?
  2. Because it's a Quarkus issue, the metadata you submitted in your patch isn't right for the GraalVM Reachability repository. Correct?
  3. The only way to ship a "Quarkus-only GraalVM config" without touching the library is to write a Quarkus Extension. Correct?
  4. How would a library author ship "Delaying class initialization configuration info" for Quarkus in a JAR?

@Karm
Copy link
Member

Karm commented Feb 16, 2023

@ksilz

1. This is not a general GraalVM issue, as [my Spring Boot version](https://github.com/ksilz/nativethumbnails-spring-boot) with the very same code builds fine to native. This is a Quarkus-only issue, as it defaults to build-time initialization. Correct?

Correct.

2. Because it's a Quarkus issue, the metadata you submitted in your patch isn't right for the [GraalVM Reachability repository](https://github.com/oracle/graalvm-reachability-metadata). Correct?

I wouldn't say it's an issue. It's a feature : ) The decision to go build-time-init first makes things more convoluted for Quarkus and Quarkus Extensions developers, yet it yields improved memory footprint, startup times and overall image size to users who code their apps using Quarkus.

Ad GraalVM Reachability metadata: Quarkus doesn't use it. The additional metadata I provided in ksilz/bug-quarkus-awt-extension#1 form just a tiny tip of what would be needed, because Quarkus AWT extension does a lot on its own. You can take a look at this small demo I prepared: https://github.com/Karm/dev-null/blob/main/thumbsup/src/main/java/thumbsup/Main.java#L31
The metadata generated by the agent in src/main/resources/META-INF/native-image sums up what would be needed.

3. The only way to ship a "Quarkus-only GraalVM config" without touching the library is to write a Quarkus Extension. Correct?

Correct. One would make a Quarkus Extension.

4. How would a library author ship "Delaying class initialization configuration info" for Quarkus in a JAR?

One can package JSON config and properties file META-INF/native-image/ as demonstrated in the example, i.e. if Thumbnailnator already had those files you as a user wouldn't have to fiddle with the agent. Such metadata cannot be specific to Quarkus though.

Hope this helps.

@ksilz
Copy link
Author

ksilz commented Feb 17, 2023

@Karm Thank you! You answered all my questions.

@ksilz
Copy link
Author

ksilz commented Mar 1, 2023

@Karm sorry, two follow-ups to your answer on Quarkus defaulting to "build-time first initialization of classes".

  1. In my experience, application code classes typically have very few static initializers or static fields that need extensive initialization. Are the JDK, frameworks & libraries different in that they have much more static initializers or static fields that need extensive initialization?
  2. I think GraalVM Native Image only does build-time initialization of classes if it "determined that's safe" (my words). Does Quarkus check in any way whether build-time initialization is safe?

@galderz
Copy link
Member

galderz commented Mar 10, 2023

  1. I think GraalVM Native Image only does build-time initialization of classes if it "determined that's safe" (my words). Does Quarkus check in any way whether build-time initialization is safe?

Yes, GraalVM might choose to initialize a class at build time if it considers it safe. I'm not aware of a detailed explanation of this but you can see examples of when it initializes class at build time vs runtime in this test class. No, Quarkus does not do any checks to see if classes at safe to be build-time init.

Having said all of this, a new class initialization strategy is currently being worked on GraalVM. A short description of this new strategy is that re-initialize at runtime would be the default. The aim is to get rid of most build time vs run time initialization errors.

@galderz
Copy link
Member

galderz commented Mar 10, 2023

Having said all of this, a new class initialization strategy is currently being worked on GraalVM. A short description of this new strategy is that re-initialize at runtime would be the default. The aim is to get rid of most build time vs run time initialization errors.

Details on the new class initialization strategy can be found here.

@ksilz
Copy link
Author

ksilz commented Mar 13, 2023

@galderz Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants