View Javadoc
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 }