View Javadoc

    1   /*
    2    * Copyright 2009 Red Hat, Inc.
    3    *
    4    * Red Hat licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the
    6    * License.  You may obtain a copy of the License at:
    7    *
    8    *    http://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 org.jboss.netty.handler.timeout;
    17
    18  import static org.jboss.netty.channel.Channels.*;
    19
    20  import java.util.concurrent.TimeUnit;
    21
    22  import org.jboss.netty.bootstrap.ServerBootstrap;
    23  import org.jboss.netty.channel.Channel;
    24  import org.jboss.netty.channel.ChannelHandlerContext;
    25  import org.jboss.netty.channel.ChannelPipeline;
    26  import org.jboss.netty.channel.ChannelPipelineFactory;
    27  import org.jboss.netty.channel.ChannelStateEvent;
    28  import org.jboss.netty.channel.Channels;
    29  import org.jboss.netty.channel.LifeCycleAwareChannelHandler;
    30  import org.jboss.netty.channel.MessageEvent;
    31  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
    32  import org.jboss.netty.channel.WriteCompletionEvent;
    33  import org.jboss.netty.util.ExternalResourceReleasable;
    34  import org.jboss.netty.util.HashedWheelTimer;
    35  import org.jboss.netty.util.Timeout;
    36  import org.jboss.netty.util.Timer;
    37  import org.jboss.netty.util.TimerTask;
    38
    39  /**
    40   * Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
    41   * read, write, or both operation for a while.
    42   *
    43   * <h3>Supported idle states</h3>
    44   * <table border="1">
    45   * <tr>
    46   * <th>Property</th><th>Meaning</th>
    47   * </tr>
    48   * <tr>
    49   * <td>{@code readerIdleTime}</td>
    50   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
    51   *     will be triggered when no read was performed for the specified period of
    52   *     time.  Specify {@code 0} to disable.</td>
    53   * </tr>
    54   * <tr>
    55   * <td>{@code writerIdleTime}</td>
    56   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
    57   *     will be triggered when no write was performed for the specified period of
    58   *     time.  Specify {@code 0} to disable.</td>
    59   * </tr>
    60   * <tr>
    61   * <td>{@code allIdleTime}</td>
    62   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
    63   *     will be triggered when neither read nor write was performed for the
    64   *     specified period of time.  Specify {@code 0} to disable.</td>
    65   * </tr>
    66   * </table>
    67   *
    68   * <pre>
    69   * // An example that sends a ping message when there is no outbound traffic
    70   * // for 30 seconds.  The connection is closed when there is no inbound traffic
    71   * // for 60 seconds.
    72   *
    73   * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
    74   *
    75   *     private final {@link Timer} timer;
    76   *
    77   *     public MyPipelineFactory({@link Timer} timer) {
    78   *         this.timer = timer;
    79   *     }
    80   *
    81   *     public {@link ChannelPipeline} getPipeline() {
    82   *         return {@link Channels}.pipeline(
    83   *             <b>new {@link IdleStateHandler}(timer, 60, 30, 0), // timer must be shared.</b>
    84   *             new MyHandler());
    85   *     }
    86   * }
    87   *
    88   * // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
    89   * public class MyHandler extends {@link IdleStateAwareChannelHandler} {
    90   *
    91   *     {@code @Override}
    92   *     public void channelIdle({@link ChannelHandlerContext} ctx, {@link IdleStateEvent} e) {
    93   *         if (e.getState() == {@link IdleState}.READER_IDLE) {
    94   *             e.getChannel().close();
    95   *         } else if (e.getState() == {@link IdleState}.WRITER_IDLE) {
    96   *             e.getChannel().write(new PingMessage());
    97   *         }
    98   *     }
    99   * }
    100  *
    101  * {@link ServerBootstrap} bootstrap = ...;
    102  * {@link Timer} timer = new {@link HashedWheelTimer}();
    103  * ...
    104  * bootstrap.setPipelineFactory(new MyPipelineFactory(timer));
    105  * ...
    106  * </pre>
    107  *
    108  * The {@link Timer} which was specified when the {@link ReadTimeoutHandler} is
    109  * created should be stopped manually by calling {@link #releaseExternalResources()}
    110  * or {@link Timer#stop()} when your application shuts down.
    111  *
    112  * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
    113  * @author <a href="http://gleamynode.net/">Trustin Lee</a>
    114  * @version $Rev: 2224 $, $Date: 2010-03-30 17:02:32 +0900 (Tue, 30 Mar 2010) $
    115  *
    116  * @see ReadTimeoutHandler
    117  * @see WriteTimeoutHandler
    118  *
    119  * @apiviz.landmark
    120  * @apiviz.uses org.jboss.netty.util.HashedWheelTimer
    121  * @apiviz.has org.jboss.netty.handler.timeout.IdleStateEvent oneway - - triggers
    122  */
    123 public class IdleStateHandler extends SimpleChannelUpstreamHandler
    124                              implements LifeCycleAwareChannelHandler,
    125                                         ExternalResourceReleasable {
    126
    127     final Timer timer;
    128
    129     final long readerIdleTimeMillis;
    130     volatile Timeout readerIdleTimeout;
    131     volatile long lastReadTime;
    132
    133     final long writerIdleTimeMillis;
    134     volatile Timeout writerIdleTimeout;
    135     volatile long lastWriteTime;
    136
    137     final long allIdleTimeMillis;
    138     volatile Timeout allIdleTimeout;
    139
    140     /**
    141      * Creates a new instance.
    142      *
    143      * @param timer
    144      *        the {@link Timer} that is used to trigger the scheduled event.
    145      *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
    146      * @param readerIdleTimeSeconds
    147      *        an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
    148      *        will be triggered when no read was performed for the specified
    149      *        period of time.  Specify {@code 0} to disable.
    150      * @param writerIdleTimeSeconds
    151      *        an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
    152      *        will be triggered when no write was performed for the specified
    153      *        period of time.  Specify {@code 0} to disable.
    154      * @param allIdleTimeSeconds
    155      *        an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
    156      *        will be triggered when neither read nor write was performed for
    157      *        the specified period of time.  Specify {@code 0} to disable.
    158      */
    159     public IdleStateHandler(
    160             Timer timer,
    161             int readerIdleTimeSeconds,
    162             int writerIdleTimeSeconds,
    163             int allIdleTimeSeconds) {
    164
    165         this(timer,
    166              readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
    167              TimeUnit.SECONDS);
    168     }
    169
    170     /**
    171      * Creates a new instance.
    172      *
    173      * @param timer
    174      *        the {@link Timer} that is used to trigger the scheduled event.
    175      *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
    176      * @param readerIdleTime
    177      *        an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
    178      *        will be triggered when no read was performed for the specified
    179      *        period of time.  Specify {@code 0} to disable.
    180      * @param writerIdleTime
    181      *        an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
    182      *        will be triggered when no write was performed for the specified
    183      *        period of time.  Specify {@code 0} to disable.
    184      * @param allIdleTime
    185      *        an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
    186      *        will be triggered when neither read nor write was performed for
    187      *        the specified period of time.  Specify {@code 0} to disable.
    188      * @param unit
    189      *        the {@link TimeUnit} of {@code readerIdleTime},
    190      *        {@code writeIdleTime}, and {@code allIdleTime}
    191      */
    192     public IdleStateHandler(
    193             Timer timer,
    194             long readerIdleTime, long writerIdleTime, long allIdleTime,
    195             TimeUnit unit) {
    196
    197         if (timer == null) {
    198             throw new NullPointerException("timer");
    199         }
    200         if (unit == null) {
    201             throw new NullPointerException("unit");
    202         }
    203
    204         this.timer = timer;
    205         if (readerIdleTime <= 0) {
    206             readerIdleTimeMillis = 0;
    207         } else {
    208             readerIdleTimeMillis = Math.max(unit.toMillis(readerIdleTime), 1);
    209         }
    210         if (writerIdleTime <= 0) {
    211             writerIdleTimeMillis = 0;
    212         } else {
    213             writerIdleTimeMillis = Math.max(unit.toMillis(writerIdleTime), 1);
    214         }
    215         if (allIdleTime <= 0) {
    216             allIdleTimeMillis = 0;
    217         } else {
    218             allIdleTimeMillis = Math.max(unit.toMillis(allIdleTime), 1);
    219         }
    220     }
    221
    222     /**
    223      * Stops the {@link Timer} which was specified in the constructor of this
    224      * handler.  You should not call this method if the {@link Timer} is in use
    225      * by other objects.
    226      */
    227     public void releaseExternalResources() {
    228         timer.stop();
    229     }
    230
    231     public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
    232         if (ctx.getPipeline().isAttached()) {
    233             // channelOpen event has been fired already, which means
    234             // this.channelOpen() will not be invoked.
    235             // We have to initialize here instead.
    236             initialize(ctx);
    237         } else {
    238             // channelOpen event has not been fired yet.
    239             // this.channelOpen() will be invoked and initialization will occur there.
    240         }
    241     }
    242
    243     public void afterAdd(ChannelHandlerContext ctx) throws Exception {
    244         // NOOP
    245     }
    246
    247     public void beforeRemove(ChannelHandlerContext ctx) throws Exception {
    248         destroy();
    249     }
    250
    251     public void afterRemove(ChannelHandlerContext ctx) throws Exception {
    252         // NOOP
    253     }
    254
    255     @Override
    256     public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
    257             throws Exception {
    258         // This method will be invoked only if this handler was added
    259         // before channelOpen event is fired.  If a user adds this handler
    260         // after the channelOpen event, initialize() will be called by beforeAdd().
    261         initialize(ctx);
    262         ctx.sendUpstream(e);
    263     }
    264
    265     @Override
    266     public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
    267             throws Exception {
    268         destroy();
    269         ctx.sendUpstream(e);
    270     }
    271
    272     @Override
    273     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
    274             throws Exception {
    275         lastReadTime = System.currentTimeMillis();
    276         ctx.sendUpstream(e);
    277     }
    278
    279     @Override
    280     public void writeComplete(ChannelHandlerContext ctx, WriteCompletionEvent e)
    281             throws Exception {
    282         if (e.getWrittenAmount() > 0) {
    283             lastWriteTime = System.currentTimeMillis();
    284         }
    285         ctx.sendUpstream(e);
    286     }
    287
    288     private void initialize(ChannelHandlerContext ctx) {
    289         lastReadTime = lastWriteTime = System.currentTimeMillis();
    290         if (readerIdleTimeMillis > 0) {
    291             readerIdleTimeout = timer.newTimeout(
    292                     new ReaderIdleTimeoutTask(ctx),
    293                     readerIdleTimeMillis, TimeUnit.MILLISECONDS);
    294         }
    295         if (writerIdleTimeMillis > 0) {
    296             writerIdleTimeout = timer.newTimeout(
    297                     new WriterIdleTimeoutTask(ctx),
    298                     writerIdleTimeMillis, TimeUnit.MILLISECONDS);
    299         }
    300         if (allIdleTimeMillis > 0) {
    301             allIdleTimeout = timer.newTimeout(
    302                     new AllIdleTimeoutTask(ctx),
    303                     allIdleTimeMillis, TimeUnit.MILLISECONDS);
    304         }
    305     }
    306
    307     private void destroy() {
    308         if (readerIdleTimeout != null) {
    309             readerIdleTimeout.cancel();
    310             readerIdleTimeout = null;
    311         }
    312         if (writerIdleTimeout != null) {
    313             writerIdleTimeout.cancel();
    314             writerIdleTimeout = null;
    315         }
    316         if (allIdleTimeout != null) {
    317             allIdleTimeout.cancel();
    318             allIdleTimeout = null;
    319         }
    320     }
    321
    322     protected void channelIdle(
    323             ChannelHandlerContext ctx, IdleState state, long lastActivityTimeMillis) throws Exception {
    324         ctx.sendUpstream(new DefaultIdleStateEvent(ctx.getChannel(), state, lastActivityTimeMillis));
    325     }
    326
    327     private final class ReaderIdleTimeoutTask implements TimerTask {
    328
    329         private final ChannelHandlerContext ctx;
    330
    331         ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
    332             this.ctx = ctx;
    333         }
    334
    335         public void run(Timeout timeout) throws Exception {
    336             if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
    337                 return;
    338             }
    339
    340             long currentTime = System.currentTimeMillis();
    341             long lastReadTime = IdleStateHandler.this.lastReadTime;
    342             long nextDelay = readerIdleTimeMillis - (currentTime - lastReadTime);
    343             if (nextDelay <= 0) {
    344                 // Reader is idle - set a new timeout and notify the callback.
    345                 readerIdleTimeout =
    346                     timer.newTimeout(this, readerIdleTimeMillis, TimeUnit.MILLISECONDS);
    347                 try {
    348                     channelIdle(ctx, IdleState.READER_IDLE, lastReadTime);
    349                 } catch (Throwable t) {
    350                     fireExceptionCaught(ctx, t);
    351                 }
    352             } else {
    353                 // Read occurred before the timeout - set a new timeout with shorter delay.
    354                 readerIdleTimeout =
    355                     timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
    356             }
    357         }
    358
    359     }
    360
    361     private final class WriterIdleTimeoutTask implements TimerTask {
    362
    363         private final ChannelHandlerContext ctx;
    364
    365         WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
    366             this.ctx = ctx;
    367         }
    368
    369         public void run(Timeout timeout) throws Exception {
    370             if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
    371                 return;
    372             }
    373
    374             long currentTime = System.currentTimeMillis();
    375             long lastWriteTime = IdleStateHandler.this.lastWriteTime;
    376             long nextDelay = writerIdleTimeMillis - (currentTime - lastWriteTime);
    377             if (nextDelay <= 0) {
    378                 // Writer is idle - set a new timeout and notify the callback.
    379                 writerIdleTimeout =
    380                     timer.newTimeout(this, writerIdleTimeMillis, TimeUnit.MILLISECONDS);
    381                 try {
    382                     channelIdle(ctx, IdleState.WRITER_IDLE, lastWriteTime);
    383                 } catch (Throwable t) {
    384                     fireExceptionCaught(ctx, t);
    385                 }
    386             } else {
    387                 // Write occurred before the timeout - set a new timeout with shorter delay.
    388                 writerIdleTimeout =
    389                     timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
    390             }
    391         }
    392     }
    393
    394     private final class AllIdleTimeoutTask implements TimerTask {
    395
    396         private final ChannelHandlerContext ctx;
    397
    398         AllIdleTimeoutTask(ChannelHandlerContext ctx) {
    399             this.ctx = ctx;
    400         }
    401
    402         public void run(Timeout timeout) throws Exception {
    403             if (timeout.isCancelled() || !ctx.getChannel().isOpen()) {
    404                 return;
    405             }
    406
    407             long currentTime = System.currentTimeMillis();
    408             long lastIoTime = Math.max(lastReadTime, lastWriteTime);
    409             long nextDelay = allIdleTimeMillis - (currentTime - lastIoTime);
    410             if (nextDelay <= 0) {
    411                 // Both reader and writer are idle - set a new timeout and
    412                 // notify the callback.
    413                 allIdleTimeout =
    414                     timer.newTimeout(this, allIdleTimeMillis, TimeUnit.MILLISECONDS);
    415                 try {
    416                     channelIdle(ctx, IdleState.ALL_IDLE, lastIoTime);
    417                 } catch (Throwable t) {
    418                     fireExceptionCaught(ctx, t);
    419                 }
    420             } else {
    421                 // Either read or write occurred before the timeout - set a new
    422                 // timeout with shorter delay.
    423                 allIdleTimeout =
    424                     timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
    425             }
    426         }
    427     }
    428 }