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((ChannelFutureListener) future -> notifyConnect(future, promise));
182                 }
183             } else {
184                 EventLoop loop = ch.eventLoop();
185                 if (loop.inEventLoop()) {
186                     doHealthCheck(ch, promise);
187                 } else {
188                     loop.execute(new Runnable() {
189                         @Override
190                         public void run() {
191                             doHealthCheck(ch, promise);
192                         }
193                     });
194                 }
195             }
196         } catch (Throwable cause) {
197             promise.tryFailure(cause);
198         }
199         return promise;
200     }
201 
202     private void notifyConnect(ChannelFuture future, Promise<Channel> promise) {
203         Channel channel = null;
204         try {
205             if (future.isSuccess()) {
206                 channel = future.channel();
207                 handler.channelAcquired(channel);
208                 if (!promise.trySuccess(channel)) {
209                     // Promise was completed in the meantime (like cancelled), just release the channel again
210                     release(channel);
211                 }
212             } else {
213                 promise.tryFailure(future.cause());
214             }
215         } catch (Throwable cause) {
216             closeAndFail(channel, cause, promise);
217         }
218     }
219 
220     private void doHealthCheck(final Channel channel, final Promise<Channel> promise) {
221         try {
222             assert channel.eventLoop().inEventLoop();
223             Future<Boolean> f = healthCheck.isHealthy(channel);
224             if (f.isDone()) {
225                 notifyHealthCheck(f, channel, promise);
226             } else {
227                 f.addListener((FutureListener<Boolean>) future -> notifyHealthCheck(future, channel, promise));
228             }
229         } catch (Throwable cause) {
230             closeAndFail(channel, cause, promise);
231         }
232     }
233 
234     private void notifyHealthCheck(Future<Boolean> future, Channel channel, Promise<Channel> promise) {
235         try {
236             assert channel.eventLoop().inEventLoop();
237             if (future.isSuccess() && future.getNow()) {
238                 channel.attr(POOL_KEY).set(this);
239                 handler.channelAcquired(channel);
240                 if (!promise.trySuccess(channel)) {
241                     // Promise was completed in the meantime (like cancelled), just release the channel again
242                     release(channel);
243                 }
244             } else {
245                 closeChannel(channel);
246                 acquireHealthyFromPoolOrNew(promise);
247             }
248         } catch (Throwable cause) {
249             closeAndFail(channel, cause, promise);
250         }
251     }
252 
253     /**
254      * Bootstrap a new {@link Channel}. The default implementation uses {@link Bootstrap#connect()}, sub-classes may
255      * override this.
256      * <p>
257      * The {@link Bootstrap} that is passed in here is cloned via {@link Bootstrap#clone()}, so it is safe to modify.
258      */
259     protected ChannelFuture connectChannel(Bootstrap bs) {
260         return bs.connect();
261     }
262 
263     @Override
264     public final Future<Void> release(Channel channel) {
265         return release(channel, channel.eventLoop().<Void>newPromise());
266     }
267 
268     @Override
269     public Future<Void> release(final Channel channel, final Promise<Void> promise) {
270         try {
271             checkNotNull(channel, "channel");
272             checkNotNull(promise, "promise");
273             EventLoop loop = channel.eventLoop();
274             if (loop.inEventLoop()) {
275                 doReleaseChannel(channel, promise);
276             } else {
277                 loop.execute(new Runnable() {
278                     @Override
279                     public void run() {
280                         doReleaseChannel(channel, promise);
281                     }
282                 });
283             }
284         } catch (Throwable cause) {
285             closeAndFail(channel, cause, promise);
286         }
287         return promise;
288     }
289 
290     private void doReleaseChannel(Channel channel, Promise<Void> promise) {
291         try {
292             assert channel.eventLoop().inEventLoop();
293             // Remove the POOL_KEY attribute from the Channel and check if it was acquired from this pool, if not fail.
294             if (channel.attr(POOL_KEY).getAndSet(null) != this) {
295                 closeAndFail(channel,
296                              // Better include a stacktrace here as this is an user error.
297                              new ChannelNotAcquiredFromPoolException(channel),
298                              promise);
299             } else {
300                 if (releaseHealthCheck) {
301                     doHealthCheckOnRelease(channel, promise);
302                 } else {
303                     releaseAndOffer(channel, promise);
304                 }
305             }
306         } catch (Throwable cause) {
307             closeAndFail(channel, cause, promise);
308         }
309     }
310 
311     private void doHealthCheckOnRelease(final Channel channel, final Promise<Void> promise) throws Exception {
312         final Future<Boolean> f = healthCheck.isHealthy(channel);
313         if (f.isDone()) {
314             releaseAndOfferIfHealthy(channel, promise, f);
315         } else {
316             f.addListener((FutureListener<Boolean>) future -> releaseAndOfferIfHealthy(channel, promise, f));
317         }
318     }
319 
320     /**
321      * Adds the channel back to the pool only if the channel is healthy.
322      * @param channel the channel to put back to the pool
323      * @param promise offer operation promise.
324      * @param future the future that contains information fif channel is healthy or not.
325      * @throws Exception in case when failed to notify handler about release operation.
326      */
327     private void releaseAndOfferIfHealthy(Channel channel, Promise<Void> promise, Future<Boolean> future) {
328         try {
329             if (future.getNow()) { //channel turns out to be healthy, offering and releasing it.
330                 releaseAndOffer(channel, promise);
331             } else { //channel not healthy, just releasing it.
332                 handler.channelReleased(channel);
333                 closeChannel(channel);
334                 promise.setSuccess(null);
335             }
336         } catch (Throwable cause) {
337             closeAndFail(channel, cause, promise);
338         }
339     }
340 
341     private void releaseAndOffer(Channel channel, Promise<Void> promise) throws Exception {
342         if (offerChannel(channel)) {
343             handler.channelReleased(channel);
344             promise.setSuccess(null);
345         } else {
346             closeAndFail(channel, new ChannelPoolFullException(), promise);
347         }
348     }
349 
350     private void closeChannel(Channel channel) throws Exception {
351         channel.attr(POOL_KEY).getAndSet(null);
352         channel.close();
353     }
354 
355     private void closeAndFail(Channel channel, Throwable cause, Promise<?> promise) {
356         if (channel != null) {
357             try {
358                 closeChannel(channel);
359             } catch (Throwable t) {
360                 promise.tryFailure(t);
361             }
362         }
363         promise.tryFailure(cause);
364     }
365 
366     /**
367      * Poll a {@link Channel} out of the internal storage to reuse it. This will return {@code null} if no
368      * {@link Channel} is ready to be reused.
369      *
370      * Sub-classes may override {@link #pollChannel()} and {@link #offerChannel(Channel)}. Be aware that
371      * implementations of these methods needs to be thread-safe!
372      */
373     protected Channel pollChannel() {
374         return lastRecentUsed ? deque.pollLast() : deque.pollFirst();
375     }
376 
377     /**
378      * Offer a {@link Channel} back to the internal storage. This will return {@code true} if the {@link Channel}
379      * could be added, {@code false} otherwise.
380      *
381      * Sub-classes may override {@link #pollChannel()} and {@link #offerChannel(Channel)}. Be aware that
382      * implementations of these methods needs to be thread-safe!
383      */
384     protected boolean offerChannel(Channel channel) {
385         return deque.offer(channel);
386     }
387 
388     @Override
389     public void close() {
390         for (;;) {
391             Channel channel = pollChannel();
392             if (channel == null) {
393                 break;
394             }
395             // Just ignore any errors that are reported back from close().
396             channel.close().awaitUninterruptibly();
397         }
398     }
399 
400     /**
401      * Closes the pool in an async manner.
402      *
403      * @return Future which represents completion of the close task
404      */
405     public Future<Void> closeAsync() {
406         // Execute close asynchronously in case this is being invoked on an eventloop to avoid blocking
407         return GlobalEventExecutor.INSTANCE.submit(new Callable<Void>() {
408             @Override
409             public Void call() throws Exception {
410                 close();
411                 return null;
412             }
413         });
414     }
415 
416     /**
417      * Fails the release of a {@link Channel} that was not acquired from this pool. This lets {@link FixedChannelPool}
418      * tell it apart from other release failures, which may also be {@link IllegalArgumentException}s thrown by user
419      * code.
420      */
421     static final class ChannelNotAcquiredFromPoolException extends IllegalArgumentException {
422 
423         private static final long serialVersionUID = -7390314262213853178L;
424 
425         ChannelNotAcquiredFromPoolException(Channel channel) {
426             super("Channel " + channel + " was not acquired from this ChannelPool");
427         }
428     }
429 
430     private static final class ChannelPoolFullException extends IllegalStateException {
431 
432         private ChannelPoolFullException() {
433             super("ChannelPool full");
434         }
435 
436         // Suppress a warning since the method doesn't need synchronization
437         @Override
438         public Throwable fillInStackTrace() {
439             return this;
440         }
441     }
442 }