1 /*
2 * Copyright 2015 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.channel.pool;
17
18 import io.netty.bootstrap.Bootstrap;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelFuture;
21 import io.netty.channel.ChannelFutureListener;
22 import io.netty.channel.ChannelInitializer;
23 import io.netty.channel.EventLoop;
24 import io.netty.util.AttributeKey;
25 import io.netty.util.concurrent.Future;
26 import io.netty.util.concurrent.FutureListener;
27 import io.netty.util.concurrent.GlobalEventExecutor;
28 import io.netty.util.concurrent.Promise;
29 import io.netty.util.internal.PlatformDependent;
30
31 import java.util.Deque;
32 import java.util.concurrent.Callable;
33
34 import static io.netty.util.internal.ObjectUtil.*;
35
36 /**
37 * Simple {@link ChannelPool} implementation which will create new {@link Channel}s if someone tries to acquire
38 * a {@link Channel} but none is in the pool atm. No limit on the maximal concurrent {@link Channel}s is enforced.
39 *
40 * This implementation uses LIFO order for {@link Channel}s in the {@link ChannelPool}.
41 *
42 */
43 public class SimpleChannelPool implements ChannelPool {
44 private static final AttributeKey<SimpleChannelPool> POOL_KEY =
45 AttributeKey.newInstance("io.netty.channel.pool.SimpleChannelPool");
46 private final Deque<Channel> deque = PlatformDependent.newConcurrentDeque();
47 private final ChannelPoolHandler handler;
48 private final ChannelHealthChecker healthCheck;
49 private final Bootstrap bootstrap;
50 private final boolean releaseHealthCheck;
51 private final boolean lastRecentUsed;
52
53 /**
54 * Creates a new instance using the {@link ChannelHealthChecker#ACTIVE}.
55 *
56 * @param bootstrap the {@link Bootstrap} that is used for connections
57 * @param handler the {@link ChannelPoolHandler} that will be notified for the different pool actions
58 */
59 public SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler) {
60 this(bootstrap, handler, ChannelHealthChecker.ACTIVE);
61 }
62
63 /**
64 * Creates a new instance.
65 *
66 * @param bootstrap the {@link Bootstrap} that is used for connections
67 * @param handler the {@link ChannelPoolHandler} that will be notified for the different pool actions
68 * @param healthCheck the {@link ChannelHealthChecker} that will be used to check if a {@link Channel} is
69 * still healthy when obtain from the {@link ChannelPool}
70 */
71 public SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck) {
72 this(bootstrap, handler, healthCheck, true);
73 }
74
75 /**
76 * Creates a new instance.
77 *
78 * @param bootstrap the {@link Bootstrap} that is used for connections
79 * @param handler the {@link ChannelPoolHandler} that will be notified for the different pool actions
80 * @param healthCheck the {@link ChannelHealthChecker} that will be used to check if a {@link Channel} is
81 * still healthy when obtain from the {@link ChannelPool}
82 * @param releaseHealthCheck will check channel health before offering back if this parameter set to {@code true};
83 * otherwise, channel health is only checked at acquisition time
84 */
85 public SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck,
86 boolean releaseHealthCheck) {
87 this(bootstrap, handler, healthCheck, releaseHealthCheck, true);
88 }
89
90 /**
91 * Creates a new instance.
92 *
93 * @param bootstrap the {@link Bootstrap} that is used for connections
94 * @param handler the {@link ChannelPoolHandler} that will be notified for the different pool actions
95 * @param healthCheck the {@link ChannelHealthChecker} that will be used to check if a {@link Channel} is
96 * still healthy when obtain from the {@link ChannelPool}
97 * @param releaseHealthCheck will check channel health before offering back if this parameter set to {@code true};
98 * otherwise, channel health is only checked at acquisition time
99 * @param lastRecentUsed {@code true} {@link Channel} selection will be LIFO, if {@code false} FIFO.
100 */
101 public SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck,
102 boolean releaseHealthCheck, boolean lastRecentUsed) {
103 this.handler = checkNotNull(handler, "handler");
104 this.healthCheck = checkNotNull(healthCheck, "healthCheck");
105 this.releaseHealthCheck = releaseHealthCheck;
106 // Clone the original Bootstrap as we want to set our own handler
107 this.bootstrap = checkNotNull(bootstrap, "bootstrap").clone();
108 this.bootstrap.handler(new ChannelInitializer<Channel>() {
109 @Override
110 protected void initChannel(Channel ch) throws Exception {
111 assert ch.eventLoop().inEventLoop();
112 handler.channelCreated(ch);
113 }
114 });
115 this.lastRecentUsed = lastRecentUsed;
116 }
117
118 /**
119 * Returns the {@link Bootstrap} this pool will use to open new connections.
120 *
121 * @return the {@link Bootstrap} this pool will use to open new connections
122 */
123 protected Bootstrap bootstrap() {
124 return bootstrap;
125 }
126
127 /**
128 * Returns the {@link ChannelPoolHandler} that will be notified for the different pool actions.
129 *
130 * @return the {@link ChannelPoolHandler} that will be notified for the different pool actions
131 */
132 protected ChannelPoolHandler handler() {
133 return handler;
134 }
135
136 /**
137 * Returns the {@link ChannelHealthChecker} that will be used to check if a {@link Channel} is healthy.
138 *
139 * @return the {@link ChannelHealthChecker} that will be used to check if a {@link Channel} is healthy
140 */
141 protected ChannelHealthChecker healthChecker() {
142 return healthCheck;
143 }
144
145 /**
146 * Indicates whether this pool will check the health of channels before offering them back into the pool.
147 *
148 * @return {@code true} if this pool will check the health of channels before offering them back into the pool, or
149 * {@code false} if channel health is only checked at acquisition time
150 */
151 protected boolean releaseHealthCheck() {
152 return releaseHealthCheck;
153 }
154
155 @Override
156 public final Future<Channel> acquire() {
157 return acquire(bootstrap.config().group().next().<Channel>newPromise());
158 }
159
160 @Override
161 public Future<Channel> acquire(final Promise<Channel> promise) {
162 return acquireHealthyFromPoolOrNew(checkNotNull(promise, "promise"));
163 }
164
165 /**
166 * Tries to retrieve healthy channel from the pool if any or creates a new channel otherwise.
167 * @param promise the promise to provide acquire result.
168 * @return future for acquiring a channel.
169 */
170 private Future<Channel> acquireHealthyFromPoolOrNew(final Promise<Channel> promise) {
171 try {
172 final Channel ch = pollChannel();
173 if (ch == null) {
174 // No Channel left in the pool bootstrap a new Channel
175 Bootstrap bs = bootstrap.clone();
176 bs.attr(POOL_KEY, this);
177 ChannelFuture f = connectChannel(bs);
178 if (f.isDone()) {
179 notifyConnect(f, promise);
180 } else {
181 f.addListener(new ChannelFutureListener() {
182 @Override
183 public void operationComplete(ChannelFuture future) throws Exception {
184 notifyConnect(future, promise);
185 }
186 });
187 }
188 } else {
189 EventLoop loop = ch.eventLoop();
190 if (loop.inEventLoop()) {
191 doHealthCheck(ch, promise);
192 } else {
193 loop.execute(new Runnable() {
194 @Override
195 public void run() {
196 doHealthCheck(ch, promise);
197 }
198 });
199 }
200 }
201 } catch (Throwable cause) {
202 promise.tryFailure(cause);
203 }
204 return promise;
205 }
206
207 private void notifyConnect(ChannelFuture future, Promise<Channel> promise) {
208 Channel channel = null;
209 try {
210 if (future.isSuccess()) {
211 channel = future.channel();
212 handler.channelAcquired(channel);
213 if (!promise.trySuccess(channel)) {
214 // Promise was completed in the meantime (like cancelled), just release the channel again
215 release(channel);
216 }
217 } else {
218 promise.tryFailure(future.cause());
219 }
220 } catch (Throwable cause) {
221 closeAndFail(channel, cause, promise);
222 }
223 }
224
225 private void doHealthCheck(final Channel channel, final Promise<Channel> promise) {
226 try {
227 assert channel.eventLoop().inEventLoop();
228 Future<Boolean> f = healthCheck.isHealthy(channel);
229 if (f.isDone()) {
230 notifyHealthCheck(f, channel, promise);
231 } else {
232 f.addListener(new FutureListener<Boolean>() {
233 @Override
234 public void operationComplete(Future<Boolean> future) {
235 notifyHealthCheck(future, channel, promise);
236 }
237 });
238 }
239 } catch (Throwable cause) {
240 closeAndFail(channel, cause, promise);
241 }
242 }
243
244 private void notifyHealthCheck(Future<Boolean> future, Channel channel, Promise<Channel> promise) {
245 try {
246 assert channel.eventLoop().inEventLoop();
247 if (future.isSuccess() && future.getNow()) {
248 channel.attr(POOL_KEY).set(this);
249 handler.channelAcquired(channel);
250 if (!promise.trySuccess(channel)) {
251 // Promise was completed in the meantime (like cancelled), just release the channel again
252 release(channel);
253 }
254 } else {
255 closeChannel(channel);
256 acquireHealthyFromPoolOrNew(promise);
257 }
258 } catch (Throwable cause) {
259 closeAndFail(channel, cause, promise);
260 }
261 }
262
263 /**
264 * Bootstrap a new {@link Channel}. The default implementation uses {@link Bootstrap#connect()}, sub-classes may
265 * override this.
266 * <p>
267 * The {@link Bootstrap} that is passed in here is cloned via {@link Bootstrap#clone()}, so it is safe to modify.
268 */
269 protected ChannelFuture connectChannel(Bootstrap bs) {
270 return bs.connect();
271 }
272
273 @Override
274 public final Future<Void> release(Channel channel) {
275 return release(channel, channel.eventLoop().<Void>newPromise());
276 }
277
278 @Override
279 public Future<Void> release(final Channel channel, final Promise<Void> promise) {
280 try {
281 checkNotNull(channel, "channel");
282 checkNotNull(promise, "promise");
283 EventLoop loop = channel.eventLoop();
284 if (loop.inEventLoop()) {
285 doReleaseChannel(channel, promise);
286 } else {
287 loop.execute(new Runnable() {
288 @Override
289 public void run() {
290 doReleaseChannel(channel, promise);
291 }
292 });
293 }
294 } catch (Throwable cause) {
295 closeAndFail(channel, cause, promise);
296 }
297 return promise;
298 }
299
300 private void doReleaseChannel(Channel channel, Promise<Void> promise) {
301 try {
302 assert channel.eventLoop().inEventLoop();
303 // Remove the POOL_KEY attribute from the Channel and check if it was acquired from this pool, if not fail.
304 if (channel.attr(POOL_KEY).getAndSet(null) != this) {
305 closeAndFail(channel,
306 // Better include a stacktrace here as this is an user error.
307 new ChannelNotAcquiredFromPoolException(channel),
308 promise);
309 } else {
310 if (releaseHealthCheck) {
311 doHealthCheckOnRelease(channel, promise);
312 } else {
313 releaseAndOffer(channel, promise);
314 }
315 }
316 } catch (Throwable cause) {
317 closeAndFail(channel, cause, promise);
318 }
319 }
320
321 private void doHealthCheckOnRelease(final Channel channel, final Promise<Void> promise) throws Exception {
322 final Future<Boolean> f = healthCheck.isHealthy(channel);
323 if (f.isDone()) {
324 releaseAndOfferIfHealthy(channel, promise, f);
325 } else {
326 f.addListener(new FutureListener<Boolean>() {
327 @Override
328 public void operationComplete(Future<Boolean> future) throws Exception {
329 releaseAndOfferIfHealthy(channel, promise, f);
330 }
331 });
332 }
333 }
334
335 /**
336 * Adds the channel back to the pool only if the channel is healthy.
337 * @param channel the channel to put back to the pool
338 * @param promise offer operation promise.
339 * @param future the future that contains information fif channel is healthy or not.
340 * @throws Exception in case when failed to notify handler about release operation.
341 */
342 private void releaseAndOfferIfHealthy(Channel channel, Promise<Void> promise, Future<Boolean> future) {
343 try {
344 if (future.getNow()) { //channel turns out to be healthy, offering and releasing it.
345 releaseAndOffer(channel, promise);
346 } else { //channel not healthy, just releasing it.
347 handler.channelReleased(channel);
348 closeChannel(channel);
349 promise.setSuccess(null);
350 }
351 } catch (Throwable cause) {
352 closeAndFail(channel, cause, promise);
353 }
354 }
355
356 private void releaseAndOffer(Channel channel, Promise<Void> promise) throws Exception {
357 if (offerChannel(channel)) {
358 handler.channelReleased(channel);
359 promise.setSuccess(null);
360 } else {
361 closeAndFail(channel, new ChannelPoolFullException(), promise);
362 }
363 }
364
365 private void closeChannel(Channel channel) throws Exception {
366 channel.attr(POOL_KEY).getAndSet(null);
367 channel.close();
368 }
369
370 private void closeAndFail(Channel channel, Throwable cause, Promise<?> promise) {
371 if (channel != null) {
372 try {
373 closeChannel(channel);
374 } catch (Throwable t) {
375 promise.tryFailure(t);
376 }
377 }
378 promise.tryFailure(cause);
379 }
380
381 /**
382 * Poll a {@link Channel} out of the internal storage to reuse it. This will return {@code null} if no
383 * {@link Channel} is ready to be reused.
384 *
385 * Sub-classes may override {@link #pollChannel()} and {@link #offerChannel(Channel)}. Be aware that
386 * implementations of these methods needs to be thread-safe!
387 */
388 protected Channel pollChannel() {
389 return lastRecentUsed ? deque.pollLast() : deque.pollFirst();
390 }
391
392 /**
393 * Offer a {@link Channel} back to the internal storage. This will return {@code true} if the {@link Channel}
394 * could be added, {@code false} otherwise.
395 *
396 * Sub-classes may override {@link #pollChannel()} and {@link #offerChannel(Channel)}. Be aware that
397 * implementations of these methods needs to be thread-safe!
398 */
399 protected boolean offerChannel(Channel channel) {
400 return deque.offer(channel);
401 }
402
403 @Override
404 public void close() {
405 for (;;) {
406 Channel channel = pollChannel();
407 if (channel == null) {
408 break;
409 }
410 // Just ignore any errors that are reported back from close().
411 channel.close().awaitUninterruptibly();
412 }
413 }
414
415 /**
416 * Closes the pool in an async manner.
417 *
418 * @return Future which represents completion of the close task
419 */
420 public Future<Void> closeAsync() {
421 // Execute close asynchronously in case this is being invoked on an eventloop to avoid blocking
422 return GlobalEventExecutor.INSTANCE.submit(new Callable<Void>() {
423 @Override
424 public Void call() throws Exception {
425 close();
426 return null;
427 }
428 });
429 }
430
431 /**
432 * Fails the release of a {@link Channel} that was not acquired from this pool. This lets {@link FixedChannelPool}
433 * tell it apart from other release failures, which may also be {@link IllegalArgumentException}s thrown by user
434 * code.
435 */
436 static final class ChannelNotAcquiredFromPoolException extends IllegalArgumentException {
437
438 private static final long serialVersionUID = -7390314262213853178L;
439
440 ChannelNotAcquiredFromPoolException(Channel channel) {
441 super("Channel " + channel + " was not acquired from this ChannelPool");
442 }
443 }
444
445 private static final class ChannelPoolFullException extends IllegalStateException {
446
447 private ChannelPoolFullException() {
448 super("ChannelPool full");
449 }
450
451 // Suppress a warning since the method doesn't need synchronization
452 @Override
453 public Throwable fillInStackTrace() {
454 return this;
455 }
456 }
457 }