View Javadoc

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