View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util;
20  
21  import java.net.InetSocketAddress;
22  import java.net.SocketAddress;
23  import java.nio.channels.UnresolvedAddressException;
24  import java.util.concurrent.Executor;
25  import java.util.concurrent.TimeUnit;
26  import java.util.concurrent.TimeoutException;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.log.Logger;
31  import org.eclipse.jetty.util.thread.Scheduler;
32  
33  /**
34   * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
35   * in order to avoid blocking on DNS lookup.
36   * <p />
37   * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
38   * the host name, and this may block for several seconds.
39   * This class creates the {@link InetSocketAddress} in a separate thread and provides the result
40   * through a {@link Promise}, with the possibility to specify a timeout for the operation.
41   * <p />
42   * Example usage:
43   * <pre>
44   * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
45   * resolver.resolve("www.google.com", 80, new Promise&lt;SocketAddress&gt;()
46   * {
47   *     public void succeeded(SocketAddress result)
48   *     {
49   *         // The address was resolved
50   *     }
51   *
52   *     public void failed(Throwable failure)
53   *     {
54   *         // The address resolution failed
55   *     }
56   * });
57   * </pre>
58   */
59  public class SocketAddressResolver
60  {
61      private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
62  
63      private final Executor executor;
64      private final Scheduler scheduler;
65      private final long timeout;
66  
67      /**
68       * Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
69       * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
70       *
71       * @param executor the thread pool to use to perform DNS resolution in pooled threads
72       * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
73       * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
74       */
75      public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
76      {
77          this.executor = executor;
78          this.scheduler = scheduler;
79          this.timeout = timeout;
80      }
81  
82      public Executor getExecutor()
83      {
84          return executor;
85      }
86  
87      public Scheduler getScheduler()
88      {
89          return scheduler;
90      }
91  
92      public long getTimeout()
93      {
94          return timeout;
95      }
96  
97      /**
98       * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
99       * with the default timeout.
100      *
101      * @param host the host to resolve
102      * @param port the port of the resulting socket address
103      * @param promise the callback invoked when the resolution succeeds or fails
104      * @see #resolve(String, int, long, Promise)
105      */
106     public void resolve(String host, int port, Promise<SocketAddress> promise)
107     {
108         resolve(host, port, timeout, promise);
109     }
110 
111     /**
112      * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
113      * with the given timeout.
114      *
115      * @param host the host to resolve
116      * @param port the port of the resulting socket address
117      * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
118      * @param promise the callback invoked when the resolution succeeds or fails
119      */
120     protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
121     {
122         executor.execute(new Runnable()
123         {
124             @Override
125             public void run()
126             {
127                 Scheduler.Task task = null;
128                 final AtomicBoolean complete = new AtomicBoolean();
129                 if (timeout > 0)
130                 {
131                     final Thread thread = Thread.currentThread();
132                     task = scheduler.schedule(new Runnable()
133                     {
134                         @Override
135                         public void run()
136                         {
137                             if (complete.compareAndSet(false, true))
138                             {
139                                 promise.failed(new TimeoutException());
140                                 thread.interrupt();
141                             }
142                         }
143                     }, timeout, TimeUnit.MILLISECONDS);
144                 }
145 
146                 try
147                 {
148                     long start = System.nanoTime();
149                     InetSocketAddress result = new InetSocketAddress(host, port);
150                     long elapsed = System.nanoTime() - start;
151                     LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
152                     if (complete.compareAndSet(false, true))
153                     {
154                         if (result.isUnresolved())
155                             promise.failed(new UnresolvedAddressException());
156                         else
157                             promise.succeeded(result);
158                     }
159                 }
160                 catch (Throwable x)
161                 {
162                     if (complete.compareAndSet(false, true))
163                         promise.failed(x);
164                 }
165                 finally
166                 {
167                     if (task != null)
168                         task.cancel();
169                     // Reset the interrupted status before releasing the thread to the pool
170                     Thread.interrupted();
171                 }
172             }
173         });
174     }
175 }