diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsCacheEntry.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsCacheEntry.java new file mode 100644 index 0000000000..dc7b9e99f2 --- /dev/null +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsCacheEntry.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015 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.channel.AddressedEnvelope; +import io.netty.channel.EventLoop; +import io.netty.handler.codec.dns.DnsResponse; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.concurrent.ScheduledFuture; +import io.netty.util.internal.OneTimeTask; +import io.netty.util.internal.PlatformDependent; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +final class DnsCacheEntry { + + private enum State { + INIT, + SCHEDULED_EXPIRATION, + RELEASED + } + + private final AddressedEnvelope response; + private final Throwable cause; + private volatile ScheduledFuture expirationFuture; + private boolean released; + + @SuppressWarnings("unchecked") + DnsCacheEntry(AddressedEnvelope response) { + this.response = (AddressedEnvelope) response.retain(); + cause = null; + } + + DnsCacheEntry(Throwable cause) { + this.cause = cause; + response = null; + } + + Throwable cause() { + return cause; + } + + synchronized AddressedEnvelope retainedResponse() { + if (released) { + // Released by other thread via either the expiration task or clearCache() + return null; + } + + return response.retain(); + } + + void scheduleExpiration(EventLoop loop, Runnable task, long delay, TimeUnit unit) { + assert expirationFuture == null: "expiration task scheduled already"; + expirationFuture = loop.schedule(task, delay, unit); + } + + void release() { + synchronized (this) { + if (released) { + return; + } + + released = true; + ReferenceCountUtil.safeRelease(response); + } + + ScheduledFuture expirationFuture = this.expirationFuture; + if (expirationFuture != null) { + expirationFuture.cancel(false); + } + } +} diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java index 1f7b6f8415..d332cb8692 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java @@ -685,16 +685,19 @@ public class DnsNameResolver extends SimpleNameResolver { final EventLoop eventLoop = ch.eventLoop(); final DnsCacheEntry cachedResult = queryCache.get(question); if (cachedResult != null) { - if (cachedResult.response != null) { - return eventLoop.newSucceededFuture(cachedResult.response.retain()); + AddressedEnvelope response = cachedResult.retainedResponse(); + if (response != null) { + return eventLoop.newSucceededFuture(response); } else { - return eventLoop.newFailedFuture(cachedResult.cause); + Throwable cause = cachedResult.cause(); + if (cause != null) { + return eventLoop.newFailedFuture(cause); + } } - } else { - return query0( - nameServerAddresses, question, - eventLoop.>newPromise()); } + return query0( + nameServerAddresses, question, + eventLoop.>newPromise()); } /** @@ -716,14 +719,18 @@ public class DnsNameResolver extends SimpleNameResolver { final DnsCacheEntry cachedResult = queryCache.get(question); if (cachedResult != null) { - if (cachedResult.response != null) { - return cast(promise).setSuccess(cachedResult.response.retain()); + AddressedEnvelope response = cachedResult.retainedResponse(); + if (response != null) { + return cast(promise).setSuccess(response); } else { - return cast(promise).setFailure(cachedResult.cause); + Throwable cause = cachedResult.cause(); + if (cause != null) { + return cast(promise).setFailure(cause); + } } - } else { - return query0(nameServerAddresses, question, promise); } + + return query0(nameServerAddresses, question, promise); } private Future> query0( @@ -739,7 +746,16 @@ public class DnsNameResolver extends SimpleNameResolver { } } - void cache(final DnsQuestion question, DnsCacheEntry entry, long delaySeconds) { + void cacheSuccess( + DnsQuestion question, AddressedEnvelope res, long delaySeconds) { + cache(question, new DnsCacheEntry(res), delaySeconds); + } + + void cacheFailure(DnsQuestion question, Throwable cause, long delaySeconds) { + cache(question, new DnsCacheEntry(cause), delaySeconds); + } + + private void cache(final DnsQuestion question, DnsCacheEntry entry, long delaySeconds) { DnsCacheEntry oldEntry = queryCache.put(question, entry); if (oldEntry != null) { oldEntry.release(); @@ -747,13 +763,15 @@ public class DnsNameResolver extends SimpleNameResolver { boolean scheduled = false; try { - entry.expirationFuture = ch.eventLoop().schedule(new OneTimeTask() { - @Override - public void run() { - clearCache(question); - } - }, delaySeconds, TimeUnit.SECONDS); - + entry.scheduleExpiration( + ch.eventLoop(), + new OneTimeTask() { + @Override + public void run() { + clearCache(question); + } + }, + delaySeconds, TimeUnit.SECONDS); scheduled = true; } finally { if (!scheduled) { @@ -852,7 +870,7 @@ public class DnsNameResolver extends SimpleNameResolver { // Ensure that the found TTL is between minTtl and maxTtl. ttl = Math.max(minTtl(), Math.min(maxTtl, ttl)); - DnsNameResolver.this.cache(question, new DnsCacheEntry(res), ttl); + DnsNameResolver.this.cacheSuccess(question, res, ttl); } @Override @@ -861,32 +879,4 @@ public class DnsNameResolver extends SimpleNameResolver { } } - static final class DnsCacheEntry { - final AddressedEnvelope response; - final Throwable cause; - volatile ScheduledFuture expirationFuture; - - @SuppressWarnings("unchecked") - DnsCacheEntry(AddressedEnvelope response) { - this.response = (AddressedEnvelope) response.retain(); - cause = null; - } - - DnsCacheEntry(Throwable cause) { - this.cause = cause; - response = null; - } - - void release() { - AddressedEnvelope response = this.response; - if (response != null) { - ReferenceCountUtil.safeRelease(response); - } - - ScheduledFuture expirationFuture = this.expirationFuture; - if (expirationFuture != null) { - expirationFuture.cancel(false); - } - } - } } diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java index f3f35cc4ff..8cc10b3b9f 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsQueryContext.java @@ -28,7 +28,6 @@ import io.netty.handler.codec.dns.DnsQuestion; import io.netty.handler.codec.dns.DnsRecord; import io.netty.handler.codec.dns.DnsRecordType; import io.netty.handler.codec.dns.DnsResponse; -import io.netty.resolver.dns.DnsNameResolver.DnsCacheEntry; import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.ScheduledFuture; import io.netty.util.internal.OneTimeTask; @@ -219,6 +218,6 @@ final class DnsQueryContext { return; } - parent.cache(question, new DnsCacheEntry(cause), negativeTtl); + parent.cacheFailure(question, cause, negativeTtl); } }