netty5/resolver-dns/src/main/java/io/netty/resolver/dns/InflightNameResolver.java
Trustin Lee a517cce92f Do not send duplicate DNS queries when the same query is in progress already
Related issue: #5179

Motivation:

When you attempt to make a lot of connection attempts to the same target
host at the same time and our DNS resolver does not have a record for it
in the cache, the DNS resolver will send as many DNS queries as the
number of connection attempts.

As a result, DNS server will reject or drop the requests, making the
name resolution attempt fail.

Modifications:

- Add InflightNameResolver that keeps the list of name resolution
  queries and subscribes to the future of the matching query instead of
  sending a duplicate query.

Result:

- The AddressResolvers created by DnsAddressResolverGroup do not send
  duplicate DNS queries anymore
2016-05-17 15:07:36 +02:00

132 lines
4.7 KiB
Java

/*
* Copyright 2016 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:
*
* 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 io.netty.resolver.dns;
import io.netty.resolver.NameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.StringUtil;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
// FIXME(trustin): Find a better name and move it to the 'resolver' module.
final class InflightNameResolver<T> implements NameResolver<T> {
private final EventExecutor executor;
private final NameResolver<T> delegate;
private final ConcurrentMap<String, Promise<T>> resolvesInProgress;
private final ConcurrentMap<String, Promise<List<T>>> resolveAllsInProgress;
InflightNameResolver(EventExecutor executor, NameResolver<T> delegate,
ConcurrentMap<String, Promise<T>> resolvesInProgress,
ConcurrentMap<String, Promise<List<T>>> resolveAllsInProgress) {
this.executor = checkNotNull(executor, "executor");
this.delegate = checkNotNull(delegate, "delegate");
this.resolvesInProgress = checkNotNull(resolvesInProgress, "resolvesInProgress");
this.resolveAllsInProgress = checkNotNull(resolveAllsInProgress, "resolveAllsInProgress");
}
@Override
public Future<T> resolve(String inetHost) {
return resolve(inetHost, executor.<T>newPromise());
}
@Override
public Future<List<T>> resolveAll(String inetHost) {
return resolveAll(inetHost, executor.<List<T>>newPromise());
}
@Override
public void close() {
delegate.close();
}
@Override
public Promise<T> resolve(String inetHost, Promise<T> promise) {
return resolve(resolvesInProgress, inetHost, promise, false);
}
@Override
public Promise<List<T>> resolveAll(String inetHost, Promise<List<T>> promise) {
return resolve(resolveAllsInProgress, inetHost, promise, true);
}
private <U> Promise<U> resolve(
final ConcurrentMap<String, Promise<U>> resolveMap,
final String inetHost, final Promise<U> promise, boolean resolveAll) {
final Promise<U> earlyPromise = resolveMap.putIfAbsent(inetHost, promise);
if (earlyPromise != null) {
// Name resolution for the specified inetHost is in progress already.
if (earlyPromise.isDone()) {
transferResult(earlyPromise, promise);
} else {
earlyPromise.addListener(new FutureListener<U>() {
@Override
public void operationComplete(Future<U> f) throws Exception {
transferResult(f, promise);
}
});
}
} else {
try {
if (resolveAll) {
@SuppressWarnings("unchecked")
final Promise<List<T>> castPromise = (Promise<List<T>>) promise; // U is List<T>
delegate.resolveAll(inetHost, castPromise);
} else {
@SuppressWarnings("unchecked")
final Promise<T> castPromise = (Promise<T>) promise; // U is T
delegate.resolve(inetHost, castPromise);
}
} finally {
if (promise.isDone()) {
resolveMap.remove(inetHost);
} else {
promise.addListener(new FutureListener<U>() {
@Override
public void operationComplete(Future<U> f) throws Exception {
resolveMap.remove(inetHost);
}
});
}
}
}
return promise;
}
private static <T> void transferResult(Future<T> src, Promise<T> dst) {
if (src.isSuccess()) {
dst.trySuccess(src.getNow());
} else {
dst.tryFailure(src.cause());
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) + '(' + delegate + ')';
}
}