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-3045 Fix GraalVM native image support for GraalVM 22.2 #1612

Merged
merged 3 commits into from
Nov 15, 2022
Merged

Conversation

absurdfarce
Copy link
Contributor

@absurdfarce absurdfarce commented Oct 13, 2022

This pull request contains content originally provided by @sdeleuze in PR 1611

@absurdfarce
Copy link
Contributor Author

I tested a Java driver built with these changes against a sample native image built using GraalVM 22.1 and 22.2. The 22.2 version failed with the errors indicated in JAVA-3045; these changes corrected those issues and allowed for the creation of a functioning native image.

GraalVM 22.1 was fine without these changes (which was consistent with our earlier testing). That version also built a working native image with these changes so backwards compatibility appears to not be an issue.

@absurdfarce
Copy link
Contributor Author

absurdfarce commented Oct 14, 2022

The more I thought about this the more something just didn't sit right. I don't love the idea of forcing dependency to be a build-time initialization via CLI params; that feels like a symptom that something else is off. So I did some testing and I'm pretty sure I broke this with my fixes for JAVA-2950. It appears that the introduction of shaded Guava classes in Dependency wound up messing with its initialization logic in 22.2.

I've confirmed that the current code cleanly builds a native image (and that that native image does what you'd expect it to) against both 22.1 and 22.2. @sdeleuze can you confirm that this addresses the problem in your test case?


Dependency(String... classNames) {
clzs = ImmutableList.copyOf(classNames);
clzs = Collections.unmodifiableList(Arrays.asList(classNames));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A proxy for immutable lists without the type information. Probably not strictly necessary (there's no obvious way for these lists to change) but it provides information that these values shouldn't change.

@@ -15,7 +15,9 @@
*/
package com.datastax.oss.driver.internal.core.util;

import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add a comment somewhere to warn future maintainers that this class should be extra careful when importing anything outside of the standard library?

Copy link
Contributor Author

@absurdfarce absurdfarce Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... I'm a little torn on this, frankly.

I'd feel better about saying something like this if I understood more cleanly why the change in question resulted in the behaviour we saw. But it seems to be the case that introducing a dependency upon a shaded class somehow:

  • caused Graal to eval this class as a runtime class
  • only did so for Graal 22.2; the original code has always worked fine on 22.1

I... just don't know if I have enough there to extract some kind of meaningful guidance. We've already seen that changes between Graal versions can have odd impacts on what was perfectly valid code. This seems like another one of those, but I just don't see a way to draw a lesson from that which can be clearly stated and apply to future use.

I'm open to suggestions if anybody has any.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah pretty hard to anticipate this kind of behavior change, I would vote for no specific comment but up to you.

@sdeleuze
Copy link

@absurdfarce I confirms this refined PR that avoid to use ImmutableList works for my use case.

@absurdfarce absurdfarce requested review from sdeleuze and adutra and removed request for sdeleuze October 14, 2022 19:47
christophstrobl added a commit to christophstrobl/spring-aot-smoke-tests that referenced this pull request Oct 21, 2022
Native tests will still fail till apache/cassandra-java-driver#1612 is resolved, still causing java.lang.NoClassDefFoundError: net/jpountz/lz4/LZ4Compressor.
@absurdfarce
Copy link
Contributor Author

A quick note on this: I believe all the dev work is done here and this is ready to be merged. I haven't done so yet because we're making some infrastructure changes internally and I'd like to get those sorted out so that I can get a good clean Jenkins run of the code base with this change. I don't anticipate any problem with that; it's really just a matter of finishing the infrastructure changes, and unfortunately that's one of quite a few issues demanding my attention.

Just wanted to keep you updated @sdeleuze

@absurdfarce
Copy link
Contributor Author

The changes mentioned in my previous commit have been delayed somewhat so I'm going to merge this as it stands in order to avoid having approved PRs lingering. I don't expect there to be any fallout from these changes but if there is it should be easy to clean up.

@absurdfarce absurdfarce merged commit a40bbc2 into 4.x Nov 15, 2022
@absurdfarce absurdfarce deleted the java3045 branch December 16, 2022 20:57
@onobc
Copy link

onobc commented Jun 3, 2023

I was trying not to ask this question on this closed issue and looked around for the answer before posting this but was unable to find the answer...

When will 4.16.0 be available?

Thanks

@absurdfarce
Copy link
Contributor Author

Hey @onobc , thanks for checking in!

I'm hoping to have 4.16.0 released next week, ideally early next week. That can always change of course but at the moment it looks like we're on track to make that happen.

@onobc
Copy link

onobc commented Jun 4, 2023

Hi @absurdfarce , thanks for the reply. Are snapshots published anywhere currently?

@absurdfarce
Copy link
Contributor Author

We don't have snapshots published anywhere @onobc , however I'm pleased to announce that 4.16.0 should now be available from Maven Central!

@onobc
Copy link

onobc commented Jun 8, 2023

Thanks for the release @absurdfarce

I attempted to use the fix in 4.16.0 on spring-data-cassandra w/ GraalVM 22.3 and it now gets past the java.lang.NoClassDefFoundError: net/jpountz/lz4/LZ4Compressor but am now seeing the previously reported

Error: Classes that should be initialized at run time got initialized during image building:
 com.datastax.oss.driver.internal.core.util.Dependency was unintentionally initialized at build time

(referenced in spring-projects/spring-data-cassandra#1340).

@sdeleuze
Copy link

sdeleuze commented Jun 8, 2023

I checked java-driver-core-4.16.0.jar content and it does not seems to include this part of my fix which explains this error, is it intentional?

@absurdfarce
Copy link
Contributor Author

@sdeleuze Unless I'm mistaken (always possible, some might say likely!) this was discussed when we originally worked on the PR. At the time it looked like removing the Guava classes solved the problem.

That was always "black magic" to a certain degree, though, so it's possible there's more going on than just that. I'll try and re-test against 4.16.0 locally in order to see if I can repro what you're seeing.

@absurdfarce
Copy link
Contributor Author

absurdfarce commented Jun 26, 2023

I tried to repro this locally but haven't had any luck yet. I added the following code snippet to my local app to very explicitly reference the dependency ops in the original report:

        MutableCodecRegistry registry = new DefaultCodecRegistry("foo");
        DseTypeCodecsRegistrar.registerDseCodecs(registry);

I then tried a matrix of all four variations of [GraalVM 22.2.0, GraalVM 22.3.1] x [ESRI dependency present, ESRI dependency missing]. In all four cases I was able to build my test app without any complaints about build-time initialization of Dependency.

Note that most of the above was redundant. The driver is already executing the relevant code when it performs it's initialization and any issue with the Dependency class should stem from the checking process (and thus would apply whether any one dependency is present or not). But I wanted to be sure.

I also tried out the sample app included in the original report to spring-data-cassandra. This also led to something of a dead end. The user didn't specify versions for the various dependencies in his pom.xml (so I'm sure what I'm getting now is different from what he got then) but at the moment I can't build anything at all. It looks like one of the Spring deps has been bumped up to Java17 in the interim; if I try to build with GraalVM 22.2.0 or 22.3.1 + Java11 I get the following:

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.0.1:process-aot (process-aot) on
 project demo: Execution process-aot of goal org.springframework.boot:spring-boot-maven-plugin:3.0.1:process-aot failed:
 Unable to load the mojo 'process-aot' in the plugin 'org.springframework.boot:spring-boot-maven-plugin:3.0.1' due to an
 API incompatibility: org.codehaus.plexus.component.repository.exception.ComponentLookupException:
 org/springframework/boot/maven/ProcessAotMojo has been compiled by a more recent version of the Java Runtime (class
 file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0   

If I try to bump my Java version up to Java17 I (quite unsurprisingly) get complaints from the Graal compiler:

Fatal error: java.lang.UnsupportedClassVersionError: com/example/demo/DemoApplicationKt has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0                                                                                                                                                                         
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)                                                                                                                                               
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)                                                                                                                                        
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)                                                                                                                         
        at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:555)                                                                                                                                    
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:458)                                                                                                                                          
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:452)                                                                                                                                          
        at java.base/java.security.AccessController.doPrivileged(Native Method)                                                                                                                                      
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:451)                                                                                                                                      
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)                                                                                                                                           
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)                                                                                                                                           
        at java.base/jdk.internal.loader.Loader.loadClass(Loader.java:559)                                                                                                                                           
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)                                                                                                                                           
        at java.base/java.lang.Class.forName0(Native Method)                                                                                                                                                         
        at java.base/java.lang.Class.forName(Class.java:398)                                                                                                                                                         
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:296)                                                                                                 
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:292)                                                                                                 
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.ImageClassLoader.forName(ImageClassLoader.java:301)                                                                                                 
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:339)                                                                          
        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)  

@onobc Do you have a more current self-contained repro case that might bring the issue to the surface?

@msupic
Copy link

msupic commented Jul 5, 2023

Also getting the same error using com.datastax.oss:java-driver-core:4.16.0:

Error: Classes that should be initialized at run time got initialized during image building:
 com.datastax.oss.driver.internal.core.util.Dependency was unintentionally initialized at build time. com.datastax.oss.driver.internal.core.util.Dependency caused initialization of this class with the following trace: 
	at com.datastax.oss.driver.internal.core.util.Dependency.<clinit>(Dependency.java:35)
	at com.datastax.dse.driver.internal.core.type.codec.DseTypeCodecsRegistrarSubstitutions$EsriMissing.getAsBoolean(DseTypeCodecsRegistrarSubstitutions.java:42)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.findTargetClass(AnnotationSubstitutionProcessor.java:1047)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleClass(AnnotationSubstitutionProcessor.java:377)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.init(AnnotationSubstitutionProcessor.java:355)
	at com.oracle.svm.hosted.NativeImageGenerator.createAnnotationSubstitutionProcessor(NativeImageGenerator.java:1003)
	at com.oracle.svm.hosted.NativeImageGenerator.setupNativeImage(NativeImageGenerator.java:888)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:579)
	at com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:539)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:408)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:612)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.start(NativeImageGeneratorRunner.java:134)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:94)

The simple example can be found in this test: https://github.com/msupic/datastax-java-driver-native/blob/master/src/test/java/org/example/JavaDriverTest.java

I can reproduce the issue using the following graalvm version:

  • Java version: 17.0.7+7, vendor version: GraalVM CE 17.0.7+7.1
  • Java version: 17.0.6+10-jvmci-22.3-b13, Version info: GraalVM 22.3.1 Java 17 CE
  • Java version: 17.0.4+8-jvmci-22.2-b06, Version info: GraalVM 22.2.0 Java 17 CE

@sdeleuze
Copy link

sdeleuze commented Jul 7, 2023

Can you please check if --initialize-at-build-time=com.datastax.oss.driver.internal.core.util.Dependency native-image buildArg fixes it?

@absurdfarce
Copy link
Contributor Author

I just tried the example from @msupic locally and had no issue:

[native-image-plugin] GraalVM Toolchain detection is disabled                                                                                                                                                        
[native-image-plugin] GraalVM location read from environment variable: JAVA_HOME                                                                                                                                     
[native-image-plugin] Native Image executable path: /work/local/graalvm-ce-java11-22.3.1/lib/svm/bin/native-image                                                                                                    
========================================================================================================================                                                                                             
GraalVM Native Image: Generating 'datastax-java-driver-native-tests' (executable)...                                                                                                                                 
========================================================================================================================                                                                                             
[1/7] Initializing...                                                                                    (8.9s @ 0.46GB)                                                                                             
 Version info: 'GraalVM 22.3.1 Java 11 CE'                                                                                                                                                                           
 Java version info: '11.0.18+10-jvmci-22.3-b13'                                                                                                                                                                      
 C compiler: gcc (redhat, aarch64, 12.1.1)                                                                                                                                                                           
 Garbage collector: Serial GC                                                                                                                                                                                        
 1 user-specific feature(s)                                                                                                                                                                                          
 - org.graalvm.junit.platform.JUnitPlatformFeature                                                                                                                                                                   
[junit-platform-native] Running in 'test listener' mode using files matching pattern [junit-platform-unique-ids*] found in folder [/work/git/datastax-java-driver-native/build/test-results/test/testlist] and its su
bfolders.                                                                                                                                                                                                            
# Printing 1 class initialization trace(s) of class(es) traced by TraceClassInitialization to: /work/git/datastax-java-driver-native/build/native/nativeTestCompile/reports/traced_class_initialization_20230710_1344
07.txt                                                                                                                                                                                                               
[2/7] Performing analysis...  [*********]                                                               (43.7s @ 3.37GB)                                                                                             
  14,809 (89.09%) of 16,622 classes reachable                                                                                                                                                                        
  25,049 (66.47%) of 37,683 fields reachable                                                                                                                                                                         
  72,920 (58.52%) of 124,610 methods reachable                                                                                                                                                                       
     402 classes,   667 fields, and 1,500 methods registered for reflection                                                                                                                                          
      71 classes,    94 fields, and    56 methods registered for JNI access                                                                                                                                          
       5 native libraries: dl, pthread, rt, stdc++, z                                                                                                                                                                
[3/7] Building universe...                                                                               (5.9s @ 3.63GB)                                                                                             
[4/7] Parsing methods...      [**]                                                                       (4.0s @ 3.40GB)                                                                                             
[5/7] Inlining methods...     [***]                                                                      (2.2s @ 2.98GB)                                                                                             
[6/7] Compiling methods...    [*****]                                                                   (27.8s @ 1.82GB)                                                                                             
[7/7] Creating image... 
...
------------------------------------------------------------------------------------------------------------------------                                                                                             
Produced artifacts:                                                                                                                                                                                                  
 /work/git/datastax-java-driver-native/build/native/nativeTestCompile/datastax-java-driver-native-tests (executable)                                                                                                 
 /work/git/datastax-java-driver-native/build/native/nativeTestCompile/datastax-java-driver-native-tests.build_artifacts.txt (txt)                                                                                    
========================================================================================================================                                                                                             
Finished generating 'datastax-java-driver-native-tests' in 1m 41s.                                                                                                                                                   
    [native-image-plugin] Native Image written to: /work/git/datastax-java-driver-native/build/native/nativeTestCompile                                                                                              
                                                                                                                                                                                                                     
BUILD SUCCESSFUL in 1m 56s                                                                                                                                                                                           
6 actionable tasks: 6 executed 

Tests actually failed to run but that was due to an issue with testcontainers understanding the config syntax from my Docker instance. It's very clear that a functioning native image was built for me locally.

All of that said, there is a critical difference between my setup and what @msupic reported:

[mersault@fedora datastax-java-driver-native]$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 11.0.18+10-jvmci-22.3-b13)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 11.0.18+10-jvmci-22.3-b13, mixed mode)

All of that said, I don't think it matters very much. It's very clear that Dependency has to be a build-time dependency; it's used in the logic that determines when certain substitutions are employed and that only makes sense at build-time. It's also pretty clear that at least some configurations (incorrectly) determine that Dependency should be a run-time dependency and error out accordingly. The logic governing how Graal determines whether something should be a build-time or run-time dependency are extremely opaque to me so I'm not sure there's a reliable way to make that logic behave. We tried that once and clearly it didn't work. So without quite a bit of additional information I'm skeptical that we can come up with a code-oriented solution that'll really fix this.

There's one more additional data point of relevance here; there's no obvious downside to including the arg specifying Dependency as a build-time dependency. It shouldn't be necessary but clearly in some cases it is... and it doesn't hurt anything if we use it all the time.

@hhughes and I discussed this earlier this morning and the unanimous verdict was to add the args to the properties file. I'll be creating a PR to do exactly that shortly. Apologies for all the confusion everyone; clearly there's more variation here than one might have expected.

@absurdfarce
Copy link
Contributor Author

absurdfarce commented Jul 10, 2023

Note: @hhughes has opened JAVA-3085 to address this follow-up work. All further activity on this issue should migrate over to the relevant PR for that ticket.

@msupic
Copy link

msupic commented Jul 11, 2023

@sdeleuze Adding Dependency to the --initialize-at-build-time fixes the issue.
@absurdfarce I wasn't also able to reproduce the issue using GraalVM 22.3.1 Java 11 CE, but as I wrote above it can be reproduced with multiple Java 17 GraalVM versions.

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

Successfully merging this pull request may close these issues.

5 participants