Skip to content

Commit eea937c

Browse files
authored
Provide GraalVM metadata and substitutions (#1357)
JAVA-5219 JAVA-5408
1 parent 7e3108b commit eea937c

File tree

20 files changed

+357
-529
lines changed

20 files changed

+357
-529
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"name":"java.lang.Object",
4+
"queryAllDeclaredMethods":true
5+
},
6+
{
7+
"name":"sun.security.provider.NativePRNG",
8+
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
9+
},
10+
{
11+
"name":"sun.security.provider.SHA",
12+
"methods":[{"name":"<init>","parameterTypes":[] }]
13+
},
14+
{
15+
"name":"org.slf4j.Logger"
16+
}
17+
]

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ ext {
5858
projectReactorVersion = '2022.0.0'
5959
junitBomVersion = '5.10.2'
6060
logbackVersion = '1.3.14'
61+
graalSdkVersion = '24.0.0'
6162
gitVersion = getGitVersion()
6263
}
6364

driver-core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies {
4646
api "io.netty:netty-buffer", optional
4747
api "io.netty:netty-transport", optional
4848
api "io.netty:netty-handler", optional
49+
compileOnly "org.graalvm.sdk:graal-sdk:$graalSdkVersion"
4950

5051
// Optionally depend on both AWS SDK v2 and v1. The driver will use v2 is present, v1 if present, or built-in functionality if
5152
// neither are present

driver-core/src/main/com/mongodb/UnixServerAddress.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package com.mongodb;
1818

1919
import com.mongodb.annotations.Immutable;
20+
import com.mongodb.internal.graalvm.substitution.UnixServerAddressSubstitution;
2021

2122
import static com.mongodb.assertions.Assertions.isTrueArgument;
2223
import static com.mongodb.assertions.Assertions.notNull;
2324

2425
/**
2526
* Represents the location of a MongoD unix domain socket.
27+
* It is {@linkplain UnixServerAddressSubstitution not supported in GraalVM native image}.
2628
*
2729
* <p>Requires the 'jnr.unixsocket' library.</p>
2830
* @since 3.7
@@ -34,10 +36,18 @@ public final class UnixServerAddress extends ServerAddress {
3436
/**
3537
* Creates a new instance
3638
* @param path the path of the MongoD unix domain socket.
39+
* @throws UnsupportedOperationException If {@linkplain UnixServerAddressSubstitution called in a GraalVM native image}.
3740
*/
3841
public UnixServerAddress(final String path) {
3942
super(notNull("The path cannot be null", path));
4043
isTrueArgument("The path must end in .sock", path.endsWith(".sock"));
44+
checkNotInGraalVmNativeImage();
45+
}
46+
47+
/**
48+
* @throws UnsupportedOperationException If {@linkplain UnixServerAddressSubstitution called in a GraalVM native image}.
49+
*/
50+
private static void checkNotInGraalVmNativeImage() {
4151
}
4252

4353
@Override

driver-core/src/main/com/mongodb/internal/dns/JndiDnsClient.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
import java.util.Hashtable;
3333
import java.util.List;
3434

35-
final class JndiDnsClient implements DnsClient {
35+
/**
36+
* <p>This class is not part of the public API and may be removed or changed at any time</p>
37+
*/
38+
public final class JndiDnsClient implements DnsClient {
3639

3740
@Override
3841
public List<String> getResourceRecordData(final String name, final String type) throws DnsException {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.internal.graalvm.substitution;
17+
18+
import com.mongodb.UnixServerAddress;
19+
import com.oracle.svm.core.annotate.Substitute;
20+
import com.oracle.svm.core.annotate.TargetClass;
21+
22+
@TargetClass(UnixServerAddress.class)
23+
public final class UnixServerAddressSubstitution {
24+
@Substitute
25+
private static void checkNotInGraalVmNativeImage() {
26+
throw new UnsupportedOperationException("UnixServerAddress is not supported in GraalVM native image");
27+
}
28+
29+
private UnixServerAddressSubstitution() {
30+
}
31+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ Args =\
1818
com.mongodb.UnixServerAddress,\
1919
com.mongodb.internal.connection.SnappyCompressor,\
2020
com.mongodb.internal.connection.ClientMetadataHelper,\
21-
com.mongodb.internal.connection.ServerAddressHelper
21+
com.mongodb.internal.connection.ServerAddressHelper,\
22+
com.mongodb.internal.dns.DefaultDnsResolver
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
[
2+
{
3+
"name":"com.mongodb.BasicDBObject",
4+
"methods":[{"name":"<init>","parameterTypes":[] }]
5+
},
6+
{
7+
"name":"com.mongodb.MongoNamespace",
8+
"allDeclaredFields":true,
9+
"queryAllDeclaredMethods":true,
10+
"queryAllDeclaredConstructors":true
11+
},
12+
{
13+
"name":"com.mongodb.WriteConcern",
14+
"allPublicFields":true
15+
},
16+
{
17+
"name":"com.mongodb.client.model.changestream.ChangeStreamDocument",
18+
"allDeclaredFields":true,
19+
"queryAllDeclaredMethods":true,
20+
"queryAllDeclaredConstructors":true,
21+
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","org.bson.BsonDocument","org.bson.BsonDocument","org.bson.BsonDocument","java.lang.Object","java.lang.Object","org.bson.BsonDocument","org.bson.BsonTimestamp","com.mongodb.client.model.changestream.UpdateDescription","org.bson.BsonInt64","org.bson.BsonDocument","org.bson.BsonDateTime","com.mongodb.client.model.changestream.SplitEvent","org.bson.BsonDocument"] }]
22+
},
23+
{
24+
"name":"com.mongodb.client.model.changestream.SplitEvent",
25+
"allDeclaredFields":true,
26+
"queryAllDeclaredMethods":true,
27+
"queryAllDeclaredConstructors":true
28+
},
29+
{
30+
"name":"com.mongodb.client.model.changestream.TruncatedArray",
31+
"allDeclaredFields":true,
32+
"queryAllDeclaredMethods":true,
33+
"queryAllDeclaredConstructors":true
34+
},
35+
{
36+
"name":"com.mongodb.client.model.changestream.UpdateDescription",
37+
"allDeclaredFields":true,
38+
"queryAllDeclaredMethods":true,
39+
"queryAllDeclaredConstructors":true,
40+
"methods":[{"name":"<init>","parameterTypes":["java.util.List","org.bson.BsonDocument","java.util.List","org.bson.BsonDocument"] }]
41+
},
42+
{
43+
"name":"java.lang.Record"
44+
},
45+
{
46+
"name":"java.lang.Thread",
47+
"fields":[{"name":"threadLocalRandomProbe"}]
48+
},
49+
{
50+
"name":"java.net.Socket",
51+
"methods":[{"name":"setOption","parameterTypes":["java.net.SocketOption","java.lang.Object"] }]
52+
},
53+
{
54+
"name":"java.security.SecureRandomParameters"
55+
},
56+
{
57+
"name":"java.util.concurrent.ForkJoinTask",
58+
"fields":[{"name":"aux"}, {"name":"status"}]
59+
},
60+
{
61+
"name":"java.util.concurrent.atomic.Striped64",
62+
"fields":[{"name":"base"}, {"name":"cellsBusy"}]
63+
},
64+
{
65+
"name":"jdk.internal.misc.Unsafe"
66+
},
67+
{
68+
"name":"jdk.net.ExtendedSocketOptions",
69+
"fields":[{"name":"TCP_KEEPCOUNT"}, {"name":"TCP_KEEPIDLE"}, {"name":"TCP_KEEPINTERVAL"}]
70+
},
71+
{
72+
"name":"org.bson.codecs.kotlin.DataClassCodecProvider"
73+
},
74+
{
75+
"name":"org.bson.codecs.kotlinx.KotlinSerializerCodecProvider"
76+
},
77+
{
78+
"name":"org.bson.codecs.record.RecordCodecProvider"
79+
},
80+
{
81+
"name":"org.slf4j.Logger"
82+
}
83+
]

driver-core/src/main/resources/META-INF/native-image/resource-config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"resources":{
33
"includes":[{
4+
"pattern":"\\QMETA-INF/services/com.mongodb.spi.dns.DnsClientProvider\\E"
5+
}, {
46
"pattern":"\\QMETA-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider\\E"
57
}]},
68
"bundles":[]

graalvm-native-image-app/build.gradle

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,31 @@ graalvmNative {
4040
// The same is true about executing the `metadataCopy` Gradle task.
4141
// This may be a manifestation of an issue with the `org.graalvm.buildtools.native` plugin.
4242
enabled = false
43-
defaultMode = 'standard'
43+
defaultMode = 'direct'
44+
def taskExecutedWithAgentAttached = 'run'
45+
modes {
46+
direct {
47+
// see https://www.graalvm.org/latest/reference-manual/native-image/metadata/ExperimentalAgentOptions
48+
options.add("config-output-dir=$buildDir/native/agent-output/$taskExecutedWithAgentAttached")
49+
// `experimental-configuration-with-origins` produces
50+
// `graalvm-native-image-app/build/native/agent-output/run/reflect-origins.txt`
51+
// and similar files that explain the origin of each of the reachability metadata piece.
52+
// However, for some reason, the actual reachability metadata is not generated when this option is enabled,
53+
// so enable it manually if you need an explanation for a specific reachability metadata entry,
54+
// and expect the build to fail.
55+
// options.add('experimental-configuration-with-origins')
56+
57+
// `experimental-class-define-support` does not seem to do what it is supposed to do.
58+
// We need this option to work if we want to support `UnixServerAddress` in native image.
59+
// Unfortunately, the tracing agent neither generates the metadata in
60+
// `graalvm-native-image-app/src/main/resources/META-INF/native-image/proxy-config.json`,
61+
// nor does it extract the bytecode of the generated classes to
62+
// `graalvm-native-image-app/src/main/resources/META-INF/native-image/agent-extracted-predefined-classes`.
63+
options.add('experimental-class-define-support')
64+
}
65+
}
4466
metadataCopy {
45-
inputTaskNames.add('run')
67+
inputTaskNames.add(taskExecutedWithAgentAttached)
4668
outputDirectories.add('src/main/resources/META-INF/native-image')
4769
mergeWithExisting = false
4870
}
@@ -93,4 +115,5 @@ dependencies {
93115
implementation "ch.qos.logback:logback-classic:$logbackVersion"
94116
implementation platform("io.projectreactor:reactor-bom:$projectReactorVersion")
95117
implementation 'io.projectreactor:reactor-core'
118+
implementation "org.graalvm.sdk:nativeimage:$graalSdkVersion"
96119
}

graalvm-native-image-app/readme.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ you need to inform Gradle about that location as specified in https://docs.gradl
4747
Assuming that your MongoDB deployment is accessible at `mongodb://localhost:27017`,
4848
run from the driver project root directory:
4949

50-
| &#x23; | Command | Description |
51-
|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
52-
| 0 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeCompile` | Build the application relying on the reachability metadata stored in `graalvm-native-image-app/src/main/resources/META-INF/native-image`. |
53-
| 1 | `env JAVA_HOME="${JDK17}" ./gradlew :graalvm-native-image-app:clean && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PjavaVersion=21 -Pagent :graalvm-native-image-app:run && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew :graalvm-native-image-app:metadataCopy` | Collect the reachability metadata and update the files storing it. Do this before building the application only if building fails otherwise. |
54-
| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. |
55-
| 3 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeRun` | Run the application using Gradle, build it if necessary relying on the stored reachability metadata. |
50+
| &#x23; | Command | Description |
51+
|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
52+
| 0 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeCompile` | Build the application relying on the reachability metadata stored in `graalvm-native-image-app/src/main/resources/META-INF/native-image`. |
53+
| 1 | `env JAVA_HOME="${JDK17}" ./gradlew clean && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew -PjavaVersion=21 -Pagent :graalvm-native-image-app:run && env JAVA_HOME=${JDK21_GRAALVM} ./gradlew :graalvm-native-image-app:metadataCopy` | Collect the reachability metadata and update the files storing it. Do this before building the application only if building fails otherwise. |
54+
| 2 | `./graalvm-native-image-app/build/native/nativeCompile/NativeImageApp` | Run the application that has been built. |
55+
| 3 | `env JAVA_HOME="${JDK17}" ./gradlew -PjavaVersion=21 :graalvm-native-image-app:nativeRun` | Run the application using Gradle, build it if necessary relying on the stored reachability metadata. |
5656

5757
#### Specifying a custom connection string
5858

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mongodb.internal.graalvm;
17+
18+
import com.mongodb.internal.dns.JndiDnsClient;
19+
import com.mongodb.spi.dns.DnsClient;
20+
import com.mongodb.spi.dns.DnsClientProvider;
21+
import com.mongodb.spi.dns.DnsException;
22+
23+
import java.util.List;
24+
25+
import static java.lang.String.format;
26+
27+
public final class CustomDnsClientProvider implements DnsClientProvider {
28+
private static volatile boolean used = false;
29+
30+
public CustomDnsClientProvider() {
31+
}
32+
33+
@Override
34+
public DnsClient create() {
35+
return new CustomDnsClient();
36+
}
37+
38+
static void assertUsed() throws AssertionError {
39+
if (!used) {
40+
throw new AssertionError(format("%s is not used", CustomDnsClientProvider.class.getSimpleName()));
41+
}
42+
}
43+
44+
private static void markUsed() {
45+
used = true;
46+
}
47+
48+
private static final class CustomDnsClient implements DnsClient {
49+
private final JndiDnsClient wrapped;
50+
51+
CustomDnsClient() {
52+
wrapped = new JndiDnsClient();
53+
}
54+
55+
@Override
56+
public List<String> getResourceRecordData(final String name, final String type) throws DnsException {
57+
markUsed();
58+
return wrapped.getResourceRecordData(name, type);
59+
}
60+
}
61+
}

graalvm-native-image-app/src/main/com/mongodb/internal/graalvm/DnsSpi.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,52 @@
1515
*/
1616
package com.mongodb.internal.graalvm;
1717

18+
import com.mongodb.MongoClientSettings;
1819
import com.mongodb.client.MongoClient;
1920
import com.mongodb.client.MongoClients;
2021
import org.slf4j.Logger;
2122
import org.slf4j.LoggerFactory;
2223

2324
import java.util.ArrayList;
25+
import java.util.concurrent.TimeUnit;
2426

2527
final class DnsSpi {
2628
private static final Logger LOGGER = LoggerFactory.getLogger(DnsSpi.class);
2729

2830
public static void main(final String... args) {
29-
LOGGER.info("Begin");
31+
useInetAddressResolverProvider(args);
32+
useDnsClientProvider();
33+
}
34+
35+
private static void useInetAddressResolverProvider(final String... args) {
3036
try (MongoClient client = args.length == 0 ? MongoClients.create() : MongoClients.create(args[0])) {
31-
LOGGER.info("Database names: {}", client.listDatabaseNames().into(new ArrayList<>()));
37+
ArrayList<String> databaseNames = client.listDatabaseNames().into(new ArrayList<>());
38+
LOGGER.info("Database names: {}", databaseNames);
3239
}
3340
CustomInetAddressResolverProvider.assertUsed();
34-
LOGGER.info("End");
41+
}
42+
43+
private static void useDnsClientProvider() {
44+
try (MongoClient client = MongoClients.create(MongoClientSettings.builder()
45+
.applyToClusterSettings(builder -> builder
46+
.srvHost("a.b.c")
47+
// `MongoClient` uses `CustomDnsClientProvider` asynchronously,
48+
// and by waiting for server selection that cannot succeed due to `a.b.c` not resolving to an IP address,
49+
// we give `MongoClient` enough time to use `CustomDnsClientProvider`.
50+
// This is a tolerable race condition for a test.
51+
.serverSelectionTimeout(2, TimeUnit.SECONDS))
52+
.build())) {
53+
ArrayList<String> databaseNames = client.listDatabaseNames().into(new ArrayList<>());
54+
LOGGER.info("Database names: {}", databaseNames);
55+
} catch (RuntimeException e) {
56+
try {
57+
CustomDnsClientProvider.assertUsed();
58+
} catch (AssertionError err) {
59+
err.addSuppressed(e);
60+
throw err;
61+
}
62+
// an exception is expected because `a.b.c` does not resolve to an IP address
63+
}
3564
}
3665

3766
private DnsSpi() {

0 commit comments

Comments
 (0)