Skip to content

Use InetAddressResolverProvider and add tests #1353

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

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions driver-core/src/main/com/mongodb/MongoClientSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,10 @@ public DnsClient getDnsClient() {
}

/**
* Gets the {@link InetAddressResolver} to use for looking up the {@link java.net.InetAddress} instances for each host.
*
* <p>If set, it will be used to look up the {@link java.net.InetAddress} for each host, via
* {@link InetAddressResolver#lookupByName(String)}. Otherwise, {@link java.net.InetAddress#getAllByName(String)} will be used.
Comment on lines -189 to -190
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 was incorrect, as "Otherwise, ..." did not take into account InetAddressResolverProvider. Builder#inetAddressResolver explains the behavior in full, so I added @see Builder#inetAddressResolver(InetAddressResolver).

* Gets the explicitly set {@link InetAddressResolver} to use for looking up the {@link java.net.InetAddress} instances for each host.
*
* @return the {@link java.net.InetAddress} resolver
* @see Builder#inetAddressResolver(InetAddressResolver)
* @since 4.10
*/
@Nullable
Expand Down Expand Up @@ -660,6 +658,7 @@ public Builder dnsClient(@Nullable final DnsClient dnsClient) {
*
* @param inetAddressResolver the InetAddress provider
* @return the {@link java.net.InetAddress} resolver
* @see #getInetAddressResolver()
* @since 4.10
*/
public Builder inetAddressResolver(@Nullable final InetAddressResolver inetAddressResolver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,27 @@
import com.mongodb.MongoSocketException;
import com.mongodb.ServerAddress;
import com.mongodb.UnixServerAddress;
import com.mongodb.lang.Nullable;
import com.mongodb.spi.dns.InetAddressResolver;
import com.mongodb.spi.dns.InetAddressResolverProvider;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* <p>This class is not part of the public API and may be removed or changed at any time</p>
*/
public final class ServerAddressHelper {
@Nullable
private static final InetAddressResolver LOCATED_INET_ADDRESS_RESOLVER = StreamSupport.stream(
ServiceLoader.load(InetAddressResolverProvider.class).spliterator(), false)
.findFirst()
.map(InetAddressResolverProvider::create)
.orElse(null);

public static ServerAddress createServerAddress(final String host) {
return createServerAddress(host, ServerAddress.defaultPort());
Expand All @@ -46,8 +56,14 @@ public static ServerAddress createServerAddress(final String host, final int por
}

public static InetAddressResolver getInetAddressResolver(final MongoClientSettings settings) {
InetAddressResolver inetAddressResolver = settings.getInetAddressResolver();
return inetAddressResolver == null ? new DefaultInetAddressResolver() : inetAddressResolver;
InetAddressResolver explicitInetAddressResolver = settings.getInetAddressResolver();
if (explicitInetAddressResolver != null) {
return explicitInetAddressResolver;
} else if (LOCATED_INET_ADDRESS_RESOLVER != null) {
return LOCATED_INET_ADDRESS_RESOLVER;
} else {
return new DefaultInetAddressResolver();
}
}

public static List<InetSocketAddress> getSocketAddresses(final ServerAddress serverAddress, final InetAddressResolver resolver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
Args = --initialize-at-run-time=com.mongodb.UnixServerAddress,com.mongodb.internal.connection.SnappyCompressor,org.bson.types.ObjectId,com.mongodb.internal.connection.ClientMetadataHelper
Args =\
--initialize-at-run-time=\
com.mongodb.UnixServerAddress,\
com.mongodb.internal.connection.SnappyCompressor,\
com.mongodb.internal.connection.ClientMetadataHelper,\
com.mongodb.internal.connection.ServerAddressHelper
Copy link
Member Author

Choose a reason for hiding this comment

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

I checked by looking at the reports generated by native-image that these \ line breaks work fine.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"resources":{
"includes":[{
"pattern":"\\QMETA-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider\\E"
Copy link
Member Author

Choose a reason for hiding this comment

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

Weirdly, everything works even without this entry, but, if I collect reachability metadata, then this entry is added there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's strange. Did you find that for any other reachability metatada? Can you suggest a hypothesis for what might be going on?

Copy link
Member Author

@stIncMale stIncMale Mar 28, 2024

Choose a reason for hiding this comment

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

Did you find that for any other reachability metatada?

I haven't checked everything, nor do I understand how some of the automatically collected resources are used, but removing the shared library resources, or the logback-test.xml resource do have an effect: the former caused runtime errors, the latter results in log entries disappearing.

Can you suggest a hypothesis for what might be going on?

I suspect, it's all about undocumented behavior that is intended to make things easier. I found the following:

Of the two experimental options mentioned in the PR comment linked above, only -H:-UseServiceLoaderFeature is recognized, and it breaks ServiceLoader completely:

  • if we don't have the META-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider resource entry, ServiceLoader silently fails to locate CustomInetAddressResolverProvider and CustomInetAddressResolverProvider.assertUsed() fails
  • if we do have the META-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider resource entry, the ServiceLoader fails with java.util.ServiceConfigurationError: com.mongodb.spi.dns.InetAddressResolverProvider: Provider com.mongodb.internal.graalvm.CustomInetAddressResolverProvider not found even if I re-collect the reachability metadata (it does not find anything new, but it should have registered CustomInetAddressResolverProvider for reflection via reflect-config.json).

P.S. Before I found the above, I did and wrote the following:

I had a uneducated guess that maybe the entry is not needed because the META-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider resource is in the same project as the one I am building, and if the resource were in a pre-built JAR that I depend on, then the resource entry would have been required.

Since you asked, I tested that guess: created a new Gradle module with the resource and CustomInetAddressResolverProvider, removed those from :graalvm-native-image-app, and added the configuration:'archives' dependency on the new module to :graalvm-native-image-app. The result is: the entry about META-INF/services/com.mongodb.spi.dns.InetAddressResolverProvider in resource-config.json is still not needed. Note that when there is no entry, the resource is actually not mentioned in the registerResource messages emitted by native-image when it builds, yet CustomInetAddressResolverProvider is still successfully located via ServiceLoader.

I don't have any other hypotheses. The behavior is really weird.

}]},
"bundles":[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.connection;

import com.mongodb.MongoClientSettings;
import com.mongodb.internal.connection.DefaultInetAddressResolver;
import com.mongodb.internal.connection.ServerAddressHelper;
import com.mongodb.spi.dns.InetAddressResolver;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;

final class ServerAddressHelperTest {
@Test
void getInetAddressResolver() {
assertAll(
() -> assertEquals(
DefaultInetAddressResolver.class,
ServerAddressHelper.getInetAddressResolver(MongoClientSettings.builder().build()).getClass()),
() -> {
InetAddressResolver resolver = new DefaultInetAddressResolver();
assertSame(
resolver,
ServerAddressHelper.getInetAddressResolver(MongoClientSettings.builder().inetAddressResolver(resolver).build()));
}
);
}
}
5 changes: 2 additions & 3 deletions graalvm-native-image-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ graalvmNative {
configureEach {
buildArgs.add('--strict-image-heap')
buildArgs.add('-H:+UnlockExperimentalVMOptions')
// see class initialization report is generated at `graalvm/build/native/nativeCompile/reports`,
// informing us on the kind of initialization for each Java class
buildArgs.add('-H:+PrintClassInitialization')
// see class initialization and other reports in `graalvm/build/native/nativeCompile/reports`
buildArgs.add('--diagnostics-mode')
Copy link
Member Author

Choose a reason for hiding this comment

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

--diagnostics-mode includes the functionality of -H:+PrintClassInitialization, and more. So we better use --diagnostics-mode.

// see the "registerResource" entries in the `native-image` built-time output,
// informing us on the resources included in the native image being built
buildArgs.add('-H:Log=registerResource:5')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.internal.graalvm;

import com.mongodb.internal.connection.DefaultInetAddressResolver;
import com.mongodb.spi.dns.InetAddressResolver;
import com.mongodb.spi.dns.InetAddressResolverProvider;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;

import static java.lang.String.format;

public final class CustomInetAddressResolverProvider implements InetAddressResolverProvider {
private static volatile boolean used = false;

public CustomInetAddressResolverProvider() {
}

@Override
public InetAddressResolver create() {
return new CustomInetAddressResolver();
}

static void assertUsed() throws AssertionError {
if (!used) {
throw new AssertionError(format("%s is not used", CustomInetAddressResolverProvider.class.getSimpleName()));
}
}

private static void markUsed() {
used = true;
}

private static final class CustomInetAddressResolver implements InetAddressResolver {
private final DefaultInetAddressResolver wrapped;

CustomInetAddressResolver() {
wrapped = new DefaultInetAddressResolver();
}

@Override
public List<InetAddress> lookupByName(final String host) throws UnknownHostException {
markUsed();
return wrapped.lookupByName(host);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.internal.graalvm;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;

final class DnsSpi {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we also have com.mongodb.spi.dns.DnsClientProvider, consider renaming this to InetAddressResolverSpi.

Copy link
Member Author

@stIncMale stIncMale Mar 28, 2024

Choose a reason for hiding this comment

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

My plan is to use a custom DnsClientProvider in DnsSpi as part of JAVA-5219, and assert it is used similarly to what I've done in the current PR. Which is why the current class name seems appropriate to me. What do you think?

private static final Logger LOGGER = LoggerFactory.getLogger(DnsSpi.class);

public static void main(final String... args) {
LOGGER.info("Begin");
try (MongoClient client = args.length == 0 ? MongoClients.create() : MongoClients.create(args[0])) {
LOGGER.info("Database names: {}", client.listDatabaseNames().into(new ArrayList<>()));
}
CustomInetAddressResolverProvider.assertUsed();
LOGGER.info("End");
}

private DnsSpi() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ final class NativeImageApp {
private static final Logger LOGGER = LoggerFactory.getLogger(NativeImageApp.class);

public static void main(final String[] args) {
LOGGER.info("Begin");
LOGGER.info("java.vendor={}, java.vm.name={}, java.version={}",
System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version"));
String[] arguments = new String[] {getConnectionStringSystemPropertyOrDefault()};
LOGGER.info("proper args={}, tour/example arguments={}", Arrays.toString(args), Arrays.toString(arguments));
List<Throwable> errors = Stream.<ThrowingRunnable>of(
new ThrowingRunnable.Named(DnsSpi.class,
() -> DnsSpi.main(arguments)),
new ThrowingRunnable.Named(gridfs.GridFSTour.class,
() -> gridfs.GridFSTour.main(arguments)),
new ThrowingRunnable.Named(documentation.CausalConsistencyExamples.class,
Expand Down Expand Up @@ -85,6 +88,7 @@ public static void main(final String[] args) {
errors.forEach(error::addSuppressed);
throw error;
}
LOGGER.info("End");
}

private NativeImageApp() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi
package com.mongodb.internal.graalvm;

import com.mongodb.lang.NonNullApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.mongodb.internal.graalvm.CustomInetAddressResolverProvider