4141import io .netty .channel .EventLoop ;
4242import io .netty .channel .socket .SocketChannel ;
4343import io .netty .handler .codec .dns .DefaultDnsQuestion ;
44+ import io .netty .handler .codec .dns .DnsQuestion ;
4445import io .netty .handler .codec .dns .DnsRawRecord ;
4546import io .netty .handler .codec .dns .DnsRecord ;
4647import io .netty .resolver .ResolvedAddressTypes ;
5657import io .netty .util .ReferenceCountUtil ;
5758import io .netty .util .concurrent .Future ;
5859import io .netty .util .concurrent .FutureListener ;
60+ import io .netty .util .concurrent .GenericFutureListener ;
5961import io .netty .util .concurrent .Promise ;
6062import org .slf4j .Logger ;
6163import org .slf4j .LoggerFactory ;
7577import java .util .List ;
7678import java .util .Map ;
7779import java .util .RandomAccess ;
80+ import java .util .function .Function ;
7881import java .util .function .IntFunction ;
7982import javax .annotation .Nullable ;
8083
@@ -127,7 +130,7 @@ final class DefaultDnsClient implements DnsClient {
127130 private static final Cancellable TERMINATED = () -> { };
128131
129132 private final EventLoopAwareNettyIoExecutor nettyIoExecutor ;
130- private final DnsNameResolver resolver ;
133+ private final DnsNameResolverDelegate resolver ;
131134 private final MinTtlCache ttlCache ;
132135 private final long maxTTLNanos ;
133136 private final long ttlJitterNanos ;
@@ -144,6 +147,117 @@ final class DefaultDnsClient implements DnsClient {
144147 private final String id ;
145148 private boolean closed ;
146149
150+ private interface DnsNameResolverDelegate {
151+ void close ();
152+
153+ Future <List <InetAddress >> resolveAll (String name );
154+
155+ Future <List <DnsRecord >> resolveAll (DnsQuestion name );
156+
157+ long queryTimeoutMillis ();
158+ }
159+
160+ private static final class SingleResolver implements DnsNameResolverDelegate {
161+ private final DnsNameResolver nettyResolver ;
162+
163+ SingleResolver (DnsNameResolver nettyResolver ) {
164+ this .nettyResolver = nettyResolver ;
165+ }
166+
167+ @ Override
168+ public void close () {
169+ nettyResolver .close ();
170+ }
171+
172+ @ Override
173+ public Future <List <InetAddress >> resolveAll (String name ) {
174+ return nettyResolver .resolveAll (name );
175+ }
176+
177+ @ Override
178+ public Future <List <DnsRecord >> resolveAll (DnsQuestion question ) {
179+ return nettyResolver .resolveAll (question );
180+ }
181+
182+ @ Override
183+ public long queryTimeoutMillis () {
184+ return nettyResolver .queryTimeoutMillis ();
185+ }
186+ }
187+
188+ private static final class BackupRequestResolver implements DnsNameResolverDelegate {
189+
190+ private final DnsNameResolver primary ;
191+ private final DnsNameResolver backup ;
192+ private final EventLoop eventLoop ;
193+
194+ // TODO: we'll want to make sure we share the cache, make our backup request interval more flexible, and also
195+ // give ourselves some sort of budget so we don't overload DNS, but these are all possible.
196+ BackupRequestResolver (DnsNameResolverBuilder builder , EventLoop eventLoop ) {
197+ primary = builder .build ();
198+ backup = builder .consolidateCacheSize (0 ).build ();
199+ this .eventLoop = eventLoop ;
200+ }
201+
202+ @ Override
203+ public void close () {
204+ try {
205+ primary .close ();
206+ } finally {
207+ backup .close ();
208+ }
209+ }
210+
211+ @ Override
212+ public Future <List <InetAddress >> resolveAll (String name ) {
213+ return compose (resolver -> resolver .resolveAll (name ));
214+ }
215+
216+ @ Override
217+ public Future <List <DnsRecord >> resolveAll (DnsQuestion name ) {
218+ return compose (resolver -> resolver .resolveAll (name ));
219+ }
220+
221+ @ Override
222+ public long queryTimeoutMillis () {
223+ return primary .queryTimeoutMillis ();
224+ }
225+
226+ private <T > Future <T > compose (Function <DnsNameResolver , Future <T >> query ) {
227+ Promise <T > result = eventLoop .newPromise ();
228+ Future <T > r1 = query .apply (primary );
229+ Future <?> timer = eventLoop .schedule (() -> join (query .apply (backup ), result , null ),
230+ 50 , MILLISECONDS );
231+ join (r1 , result , timer );
232+ return result ;
233+ }
234+
235+ private static <T > void join (Future <? extends T > future , Promise <T > promise , @ Nullable Future <?> timer ) {
236+ // transfer results from future to promise and cancellation from promise to future.
237+ future .addListener (new GenericFutureListener <Future <T >>() {
238+ @ Override
239+ public void operationComplete (Future <T > future ) {
240+ if (timer != null ) {
241+ timer .cancel (true );
242+ }
243+ if (future .isSuccess ()) {
244+ promise .trySuccess (future .getNow ());
245+ } else if (!future .isCancelled ()) {
246+ promise .tryFailure (future .cause ());
247+ }
248+ }
249+ });
250+ promise .addListener (new GenericFutureListener <Future <T >>() {
251+ @ Override
252+ public void operationComplete (Future <T > promise ) {
253+ if (promise .isCancelled ()) {
254+ future .cancel (true );
255+ }
256+ }
257+ });
258+ }
259+ }
260+
147261 DefaultDnsClient (final String id , final IoExecutor ioExecutor , final int consolidateCacheSize ,
148262 final int minTTL , final int maxTTL , final int minCacheTTL , final int maxCacheTTL ,
149263 final int negativeTTLCacheSeconds , final long ttlJitterNanos ,
@@ -229,7 +343,8 @@ final class DefaultDnsClient implements DnsClient {
229343 if (dnsServerAddressStreamProvider != null ) {
230344 builder .nameServerProvider (toNettyType (dnsServerAddressStreamProvider ));
231345 }
232- resolver = builder .build ();
346+ // resolver = new SingleResolver(builder.build());
347+ resolver = new BackupRequestResolver (builder , eventLoop );
233348 this .resolutionTimeoutMillis = resolutionTimeout != null ? resolutionTimeout .toMillis () :
234349 // Default value is chosen based on a combination of default "timeout" and "attempts" options of
235350 // /etc/resolv.conf: https://man7.org/linux/man-pages/man5/resolv.conf.5.html
0 commit comments