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<SocketAddress>()
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 }