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

Provide GraalVM metadata and substitutions #1357

Merged
merged 18 commits into from
Sep 23, 2024

Conversation

stIncMale
Copy link
Member

@stIncMale stIncMale commented Apr 3, 2024

I recommend reviewing the first four commits in this PR one at a time, as they are logically independent and don't conflict with each other.

  1. Moved native-image.properties to src/main/resources/META-INF/native-image/native-image.properties. Note how now they are immediately under the native-image folder.
  2. Ensured that MongoClient uses custom com.mongodb.spi.dns.DnsClientProviders in GraalVM via ServiceLoader.
  3. Moved driver-specific reachability metadata from :graalvm-native-image-app into the relevant driver JARs.
  4. Introduced GraalVM substitutions based on MongoClientSubstitutions in Quarkus. The introduced substitutions achieve the following:
    4.1. Assert that substitutions are indeed used via Substitutions.assertUsed, which is called from NativeImageApp.
    4.2. Explicitly throw at run time if UnixServerAddress is used. It allegedly does not work with GraalVM, we'll figure this out (or not) in JAVA-5400.
    4.2. Explicitly throw at run time if the Snappy compressor is used. I don't know why it allegedly does not work with GraalVM, we'll figure this out in JAVA-5383.

Note that I omitted some of Quarkus substitutions for various reasons:

JAVA-5219
JAVA-5408

@stIncMale stIncMale self-assigned this Apr 3, 2024
@Override
public Stream create(final ServerAddress serverAddress) {
return createInternal(serverAddress);
Copy link
Member Author

Choose a reason for hiding this comment

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

There is no way to call the original implementation of a method that is being substituted. I had to introduce createInternal as a work-around.

Choose a reason for hiding this comment

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

Yeah, something you really want this feature.... I follow the same pattern very (too) often.


import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;

/**
* Represents the location of a MongoD unix domain socket.
* It is {@linkplain SocketStreamFactorySubstitution#create(ServerAddress) not supported in GraalVM native image}.
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that because internal classes are excluded from our API docs, this linkplain produces plain text "not supported in GraalVM native image" in the generated API docs. But in an IDE we still have a working link, which is convenient. The same goes for the change in MongoCompressor.

@stIncMale stIncMale requested a review from jyemin April 3, 2024 04:48
@stIncMale
Copy link
Member Author

Hi, @cescoffier. I am unable to add you as a reviewer of this PR, but your review may be helpful. I am inviting you to take a look at this PR if you have time.

@Substitute
public Stream create(final ServerAddress serverAddress) {
if (serverAddress instanceof UnixServerAddress) {
throw new UnsupportedOperationException("UnixServerAddress is not supported in GraalVM native image");
Copy link
Member Author

@stIncMale stIncMale Apr 3, 2024

Choose a reason for hiding this comment

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

Assuming UnixServerAddress is not working properly in GraalVM native image, this is not a breaking change. But this assumptions is based solely on the substitutions made in Quarkus. The same is true for MongoCompressorSubstitution.createSnappyCompressor.

Choose a reason for hiding this comment

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

So, it might be possible to support it now. These substitutions are kind of old, like 4/5 years old and GraalVM evolved a lot in between.

Note that the class will need to be marked as a runtime only class (cannot be initialized at build time)

Copy link
Member Author

@stIncMale stIncMale Apr 7, 2024

Choose a reason for hiding this comment

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

Note that the class will need to be marked as a runtime only class (cannot be initialized at build time)

Could you please help me understand why? I made the snappy compressor work without any tricks, just by collecting the reachability metadata with the tracing agent via the org.graalvm.buildtools.native Gradle plugin. And the compression works regardless of whether I specify

  • --initialize-at-run-time=com.mongodb.internal.connection.SnappyCompressor
  • or --initialize-at-build-time=com.mongodb.internal.connection.SnappyCompressor

P.S. Given that snappy compression works with GraalVM, I removed snappy-related substitutions.

Copy link

@cescoffier cescoffier left a comment

Choose a reason for hiding this comment

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

The config makes a lot of sense. There are a few differences with what we do in Quarkus.

My main concern is around JNA/JNI, and the cost in term of image size. Maybe it can be made optional.

@Override
public Stream create(final ServerAddress serverAddress) {
return createInternal(serverAddress);

Choose a reason for hiding this comment

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

Yeah, something you really want this feature.... I follow the same pattern very (too) often.

public final class MongoCompressorSubstitution {
@Substitute
public static MongoCompressor createSnappyCompressor() {
throw new UnsupportedOperationException("Snappy compressor is not supported in GraalVM native image");

Choose a reason for hiding this comment

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

@Substitute
public Stream create(final ServerAddress serverAddress) {
if (serverAddress instanceof UnixServerAddress) {
throw new UnsupportedOperationException("UnixServerAddress is not supported in GraalVM native image");

Choose a reason for hiding this comment

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

So, it might be possible to support it now. These substitutions are kind of old, like 4/5 years old and GraalVM evolved a lot in between.

Note that the class will need to be marked as a runtime only class (cannot be initialized at build time)

@@ -0,0 +1,22 @@
[

Choose a reason for hiding this comment

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

We do not have these in Quarkus. I need to try with that. It might fix the mongo crypt issues we have in native.

@@ -0,0 +1,96 @@
[
{
"name":"com.mongodb.BasicDBObject",

Choose a reason for hiding this comment

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

We have a slightly different set of reflection registrations. But, I believe yours are more accurate.

- Improve logging
- Use `clean` instead of `:graalvm-native-image-app:clean` in `readme.md`

JAVA-5219
Given that the snappy compressor
works in GraalVM, we don't need any substitutions for it.

JAVA-5219
Having these entries results in a larger native image,
even if the shared libraries are not used by
the application. Therefore, we won't supply
the entries with our JARs, but will document
this for users and propose the `resource-config.json`
in :graalvm-native-image-app as an example
where users may pick the necessary entries from
if need be.

JAVA-5219
@stIncMale stIncMale requested a review from cescoffier April 7, 2024 09:15
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved entries about the shared libraries before any other entries, anticipating that we'll reference this file from our docs as an example.

…-core`

Without this metadata an application using
the driver won't work, and there is no other
place this metadata may come from, unless
it is gathered via the GraalVM tracing agent.

It's weird that we have to include reachability
metadata specific to JDK, but there is
nothing to do about it, other than saying that
users are to collect the metadata for their
applications themselves. The latter actually
seems like the only right way to do this, but it
conflicts with the fact that there is Oracle's
reachability metadata repository
(https://github.com/oracle/graalvm-reachability-metadata).

JAVA-5219
Throwing from the constructor of `UnixServerAddress`
seems better than from `SocketStreamFactory.create`. Not only this is more natural,
but it also prevents the creation
of a `MongoClient`, which wasn't he case previously.

JAVA-5219
Entries were removed from `graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json` as
`NativeImageApp` works without them

JAVA-5219
public final class UnixServerAddressSubstitution {
@Substitute
private static void checkNotInGraalVmNativeImage() {
throw new UnsupportedOperationException("UnixServerAddress is not supported in GraalVM native image");
Copy link
Member Author

Choose a reason for hiding this comment

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

This is actually achievable also via org.graalvm.nativeimage.ImageInfo.inImageRuntimeCode instead of the substitutions mechanism. However, I decided to still go with the substitutions

  1. It's more powerful (not strictly so).
  2. It allows us to keep the GraalVM-related code in a separate package, though we still refer to in internal API documentation comments.

@@ -0,0 +1,180 @@
[
Copy link
Contributor

Choose a reason for hiding this comment

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

This should all be removed now that they are in mongodb-crypt, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

We have a ticket for that https://jira.mongodb.org/browse/JAVA-5408. But we can do it only when a new version of mongodb-crypt is released, and the driver depends on that new version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Leaving this unresolved since we agreed to keep this PR open until after mongodb-crypt release

Goes to mongodb-crypt from driver-core/src/main/resources/META-INF/native-image/reflect-config.json:

{
  "name":"boolean",
  "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}]
},
{
  "name":"com.sun.crypto.provider.AESCipher$General",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.sun.crypto.provider.HmacCore$HmacSHA256",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"com.sun.crypto.provider.HmacCore$HmacSHA512",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"int",
  "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}]
},
{
  "name":"java.lang.Throwable",
  "methods":[{"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }]
},
{
  "name":"java.lang.reflect.Method",
  "methods":[{"name":"isVarArgs","parameterTypes":[] }]
},
{
  "name":"java.nio.Buffer"
},
{
  "name":"long",
  "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}]
},
// NativePRNG also stays in driver-core
{
  "name":"sun.security.provider.NativePRNG",
  "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
},
{
  "name":"sun.security.provider.SHA2$SHA256",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"sun.security.provider.SHA5$SHA512",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"void",
  "fields":[{"name":"OPTIONS"}, {"name":"STRING_ENCODING"}, {"name":"STRUCTURE_ALIGNMENT"}, {"name":"TYPE_MAPPER"}]
},

Goes to mongodb-crypt from graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json

{
  "name":"org.slf4j.Logger"
},

Goes to graalvm-native-image-app/build/native/agent-output/run/reflect-config.json from driver-core/src/main/resources/META-INF/native-image/reflect-config.json

{
  "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"java.io.FilePermission"
},
{
  "name":"java.lang.RuntimePermission"
},
{
  "name":"java.net.NetPermission"
},
{
  "name":"java.net.SocketPermission"
},
{
  "name":"java.net.URLPermission",
  "methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
  "name":"java.security.AllPermission"
},
{
  "name":"java.security.SecurityPermission"
},
{
  "name":"java.util.PropertyPermission"
},
{
  "name":"java.util.concurrent.atomic.AtomicBoolean",
  "fields":[{"name":"value"}]
},
{
  "name":"java.util.concurrent.atomic.AtomicReference",
  "fields":[{"name":"value"}]
},
{
  "name":"javax.smartcardio.CardPermission"
},

Goes to bson/src/main/resources/META-INF/native-image/reflect-config.json from driver-core/src/main/resources/META-INF/native-image/reflect-config.json

{
  "name":"java.lang.Object",
  "queryAllDeclaredMethods":true
},

Goes to bson/src/main/resources/META-INF/native-image/reflect-config.json from graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json

{
  "name":"org.slf4j.Logger"
},

Goes to driver-core/src/main/resources/META-INF/native-image/reflect-config.json from graalvm-native-image-app/src/main/resources/META-INF/native-image/reflect-config.json

{
  "name":"org.slf4j.Logger"
},

JAVA-5219
@jyemin
Copy link
Contributor

jyemin commented May 2, 2024

LGTM except for the one that requires waiting for the next mongodb-crypt release.

@jyemin jyemin marked this pull request as draft May 7, 2024 22:19
@jyemin
Copy link
Contributor

jyemin commented May 7, 2024

Converted to Draft PR while we await the mongodb-crypt release.

@jyemin
Copy link
Contributor

jyemin commented Aug 13, 2024

FYI @stIncMale the mongodb-crypt release has been completed.

@stIncMale stIncMale marked this pull request as ready for review September 20, 2024 20:03
Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

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

At long last, LGTM!

@stIncMale stIncMale merged commit eea937c into mongodb:master Sep 23, 2024
59 checks passed
@stIncMale stIncMale deleted the JAVA-5219 branch September 23, 2024 15:12
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.

3 participants