Skip to content

Commit

Permalink
DnsNameResolver query server address stream feature (#13873)
Browse files Browse the repository at this point in the history
Motivation: The DnsNameResolver does instantiate an instance
FastThreadLocal for each instance of the resolver that is created. This
can have an impact on the InternalThreadLocalMap static index an the
size of allocated arrays for FastThreadLocal. In addition this
FastThreadLocal is only used by the DnsNameResolver query methods and
not the resolve methods. Common usage of the DnsNameResolver by Netty
(i.e. by a Bootstrap) only uses resolves and queries are not performed.

Modification: This encapsulate the usage of the
FastThreadLocal/dnsServerAddressStreamProvider in a specific
implementation of DnsServerAddressStream that is instantiated by the
DnsNameResolverBuilder by default to keep the current behavior. The
builder now does provide a QueryServerAddressStream property that can be
set prior calling build in order to provide a specific implementation of
the stream for use case that do not whish to sustain the cost of
instantiating a non static FastThreadLocal.

Result: DnsNameResolver that do not use FastThreadLocal can now be
created.

This provides a possible solution for #9884

---------

Co-authored-by: Norman Maurer <norman_maurer@apple.com>
  • Loading branch information
vietj and normanmaurer committed Mar 1, 2024
1 parent 3cc67f2 commit 45a7543
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
import io.netty.util.NetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
Expand Down Expand Up @@ -251,14 +250,7 @@ protected DnsResponse decodeResponse(ChannelHandlerContext ctx, DatagramPacket p
private final DnsCache resolveCache;
private final AuthoritativeDnsServerCache authoritativeDnsServerCache;
private final DnsCnameCache cnameCache;

private final FastThreadLocal<DnsServerAddressStream> nameServerAddrStream =
new FastThreadLocal<DnsServerAddressStream>() {
@Override
protected DnsServerAddressStream initialValue() {
return dnsServerAddressStreamProvider.nameServerAddressStream("");
}
};
private final DnsServerAddressStream queryDnsServerAddressStream;

private final long queryTimeoutMillis;
private final int maxQueriesPerResolve;
Expand Down Expand Up @@ -387,7 +379,8 @@ public DnsNameResolver(
NoopDnsCnameCache.INSTANCE, authoritativeDnsServerCache, null,
dnsQueryLifecycleObserverFactory, queryTimeoutMillis, resolvedAddressTypes, recursionDesired,
maxQueriesPerResolve, traceEnabled, maxPayloadSize, optResourceEnabled, hostsFileEntriesResolver,
dnsServerAddressStreamProvider, searchDomains, ndots, decodeIdn, false, 0);
dnsServerAddressStreamProvider, new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider),
searchDomains, ndots, decodeIdn, false, 0);
}

DnsNameResolver(
Expand All @@ -409,6 +402,7 @@ public DnsNameResolver(
boolean optResourceEnabled,
HostsFileEntriesResolver hostsFileEntriesResolver,
DnsServerAddressStreamProvider dnsServerAddressStreamProvider,
DnsServerAddressStream queryDnsServerAddressStream,
String[] searchDomains,
int ndots,
boolean decodeIdn,
Expand All @@ -426,6 +420,7 @@ public DnsNameResolver(
this.hostsFileEntriesResolver = checkNotNull(hostsFileEntriesResolver, "hostsFileEntriesResolver");
this.dnsServerAddressStreamProvider =
checkNotNull(dnsServerAddressStreamProvider, "dnsServerAddressStreamProvider");
this.queryDnsServerAddressStream = checkNotNull(queryDnsServerAddressStream, "queryDnsServerAddressStream");
this.resolveCache = checkNotNull(resolveCache, "resolveCache");
this.cnameCache = checkNotNull(cnameCache, "cnameCache");
this.dnsQueryLifecycleObserverFactory = traceEnabled ?
Expand Down Expand Up @@ -620,6 +615,13 @@ public long queryTimeoutMillis() {
return queryTimeoutMillis;
}

/**
* Returns the dns server address stream used for DNS queries (not resolve).
*/
public DnsServerAddressStream queryDnsServerAddressStream() {
return queryDnsServerAddressStream;
}

/**
* Returns the {@link ResolvedAddressTypes} resolved by {@link #resolve(String)}.
* The default value depends on the value of the system property {@code "java.net.preferIPv6Addresses"}.
Expand Down Expand Up @@ -1253,7 +1255,7 @@ public Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> query(
}

private InetSocketAddress nextNameServerAddress() {
return nameServerAddrStream.get().next();
return queryDnsServerAddressStream.next();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public final class DnsNameResolverBuilder {
private HostsFileEntriesResolver hostsFileEntriesResolver = HostsFileEntriesResolver.DEFAULT;
private DnsServerAddressStreamProvider dnsServerAddressStreamProvider =
DnsServerAddressStreamProviders.platformDefault();
private DnsServerAddressStream queryDnsServerAddressStream;
private DnsQueryLifecycleObserverFactory dnsQueryLifecycleObserverFactory =
NoopDnsQueryLifecycleObserverFactory.INSTANCE;
private String[] searchDomains;
Expand Down Expand Up @@ -458,6 +459,20 @@ public DnsNameResolverBuilder nameServerProvider(DnsServerAddressStreamProvider
return this;
}

protected DnsServerAddressStream queryServerAddressStream() {
return this.queryDnsServerAddressStream;
}

/**
* Set the {@link DnsServerAddressStream} which provides the server address for DNS queries.
* @return {@code this}.
*/
public DnsNameResolverBuilder queryServerAddressStream(DnsServerAddressStream queryServerAddressStream) {
this.queryDnsServerAddressStream =
checkNotNull(queryServerAddressStream, "queryServerAddressStream");
return this;
}

/**
* Set the list of search domains of the resolver.
*
Expand Down Expand Up @@ -510,6 +525,11 @@ private AuthoritativeDnsServerCache newAuthoritativeDnsServerCache() {
new NameServerComparator(DnsNameResolver.preferredAddressType(resolvedAddressTypes).addressType()));
}

private DnsServerAddressStream newQueryServerAddressStream(
DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
return new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider);
}

private DnsCnameCache newCnameCache() {
return new DefaultDnsCnameCache(
intValue(minTtl, 0), intValue(maxTtl, Integer.MAX_VALUE));
Expand Down Expand Up @@ -567,6 +587,10 @@ public DnsNameResolver build() {
DnsCnameCache cnameCache = this.cnameCache != null ? this.cnameCache : newCnameCache();
AuthoritativeDnsServerCache authoritativeDnsServerCache = this.authoritativeDnsServerCache != null ?
this.authoritativeDnsServerCache : newAuthoritativeDnsServerCache();

DnsServerAddressStream queryDnsServerAddressStream = this.queryDnsServerAddressStream != null ?
this.queryDnsServerAddressStream : newQueryServerAddressStream(dnsServerAddressStreamProvider);

return new DnsNameResolver(
eventLoop,
channelFactory,
Expand All @@ -586,6 +610,7 @@ public DnsNameResolver build() {
optResourceEnabled,
hostsFileEntriesResolver,
dnsServerAddressStreamProvider,
queryDnsServerAddressStream,
searchDomains,
ndots,
decodeIdn,
Expand Down Expand Up @@ -647,6 +672,10 @@ public DnsNameResolverBuilder copy() {
copiedBuilder.nameServerProvider(dnsServerAddressStreamProvider);
}

if (queryDnsServerAddressStream != null) {
copiedBuilder.queryServerAddressStream(queryDnsServerAddressStream);
}

if (searchDomains != null) {
copiedBuilder.searchDomains(Arrays.asList(searchDomains));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2024 The Netty Project
*
* The Netty Project licenses this file to you 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:
*
* https://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 io.netty.resolver.dns;

import io.netty.util.concurrent.FastThreadLocal;

import java.net.InetSocketAddress;

/**
* A thread local based address stream for a specific hostname.
*/
final class ThreadLocalNameServerAddressStream implements DnsServerAddressStream {

private final String hostname;
private final DnsServerAddressStreamProvider dnsServerAddressStreamProvider;
private final FastThreadLocal<DnsServerAddressStream> threadLocal = new FastThreadLocal<DnsServerAddressStream>() {
@Override
protected DnsServerAddressStream initialValue() {
return dnsServerAddressStreamProvider.nameServerAddressStream(hostname);
}
};

ThreadLocalNameServerAddressStream(DnsServerAddressStreamProvider dnsServerAddressStreamProvider) {
this(dnsServerAddressStreamProvider, "");
}

ThreadLocalNameServerAddressStream(DnsServerAddressStreamProvider dnsServerAddressStreamProvider, String hostname) {
this.dnsServerAddressStreamProvider = dnsServerAddressStreamProvider;
this.hostname = hostname;
}

@Override
public InetSocketAddress next() {
return threadLocal.get().next();
}

@Override
public DnsServerAddressStream duplicate() {
return new ThreadLocalNameServerAddressStream(dnsServerAddressStreamProvider, hostname);
}

@Override
public int size() {
return threadLocal.get().size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"queryAllPublicMethods": true
},
{
"name": "io.netty.resolver.dns.DnsNameResolver$4",
"name": "io.netty.resolver.dns.DnsNameResolver$3",
"condition": {
"typeReachable": "io.netty.resolver.dns.DnsNameResolver$4"
"typeReachable": "io.netty.resolver.dns.DnsNameResolver$3"
},
"queryAllPublicMethods": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ void testDefaults() {

checkDefaultAuthoritativeDnsServerCache(
(DefaultAuthoritativeDnsServerCache) resolver.authoritativeDnsServerCache(), MAX_SUPPORTED_TTL_SECS, 0);
assertThat(resolver.queryDnsServerAddressStream()).isInstanceOf(ThreadLocalNameServerAddressStream.class);
}

@Test
Expand Down Expand Up @@ -244,4 +245,33 @@ public boolean clear(String hostname) {
return false;
}
}

@Test
void testCustomQueryDnsServerAddressStream() {
DnsServerAddressStream queryAddressStream = new TestQueryServerAddressStream();
resolver = builder.queryServerAddressStream(queryAddressStream).build();

assertThat(resolver.queryDnsServerAddressStream()).isSameAs(queryAddressStream);

resolver = builder.copy().build();
assertThat(resolver.queryDnsServerAddressStream()).isSameAs(queryAddressStream);
}

private static final class TestQueryServerAddressStream implements DnsServerAddressStream {

@Override
public InetSocketAddress next() {
throw new UnsupportedOperationException();
}

@Override
public int size() {
throw new UnsupportedOperationException();
}

@Override
public DnsServerAddressStream duplicate() {
throw new UnsupportedOperationException();
}
}
}

0 comments on commit 45a7543

Please sign in to comment.