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.ChannelHandlerContext;
    24  import org.jboss.netty.channel.ChannelPipeline;
    25  import org.jboss.netty.channel.ChannelPipelineFactory;
    26  import org.jboss.netty.channel.ChannelStateEvent;
    27  import org.jboss.netty.channel.Channels;
    28  import org.jboss.netty.channel.LifeCycleAwareChannelHandler;
    29  import org.jboss.netty.channel.MessageEvent;
    30  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
    31  import org.jboss.netty.util.ExternalResourceReleasable;
    32  import org.jboss.netty.util.HashedWheelTimer;
    33  import org.jboss.netty.util.Timeout;
    34  import org.jboss.netty.util.Timer;
    35  import org.jboss.netty.util.TimerTask;
    36
    37  /**
    38   * Raises a {@link ReadTimeoutException} when no data was read within a certain
    39   * period of time.
    40   *
    41   * <pre>
    42   * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
    43   *
    44   *     private final {@link Timer} timer;
    45   *
    46   *     public MyPipelineFactory({@link Timer} timer) {
    47   *         this.timer = timer;
    48   *     }
    49   *
    50   *     public {@link ChannelPipeline} getPipeline() {
    51   *         // An example configuration that implements 30-second read timeout:
    52   *         return {@link Channels}.pipeline(
    53   *             <b>new {@link ReadTimeoutHandler}(timer, 30), // timer must be shared.</b>
    54   *             new MyHandler());
    55   *     }
    56   * }
    57   *
    58   * {@link ServerBootstrap} bootstrap = ...;
    59   * {@link Timer} timer = new {@link HashedWheelTimer}();
    60   * ...
    61   * bootstrap.setPipelineFactory(new MyPipelineFactory(timer));
    62   * ...
    63   * </pre>
    64   *
    65   * The {@link Timer} which was specified when the {@link ReadTimeoutHandler} is
    66   * created should be stopped manually by calling {@link #releaseExternalResources()}
    67   * or {@link Timer#stop()} when your application shuts down.
    68   *
    69   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
    70   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
    71   * @version $Rev: 2222 $, $Date: 2010-03-24 14:07:27 +0900 (Wed, 24 Mar 2010) $
    72   *
    73   * @see WriteTimeoutHandler
    74   * @see IdleStateHandler
    75   *
    76   * @apiviz.landmark
    77   * @apiviz.uses org.jboss.netty.util.HashedWheelTimer
    78   * @apiviz.has org.jboss.netty.handler.timeout.TimeoutException oneway - - raises
    79   */
    80  public class ReadTimeoutHandler extends SimpleChannelUpstreamHandler
    81                                  implements LifeCycleAwareChannelHandler,
    82                                             ExternalResourceReleasable {
    83
    84      static final ReadTimeoutException EXCEPTION = new ReadTimeoutException();
    85
    86      final Timer timer;
    87      final long timeoutMillis;
    88      volatile Timeout timeout;
    89      private volatile ReadTimeoutTask task;
    90      volatile long lastReadTime;
    91
    92      /**
    93       * Creates a new instance.
    94       *
    95       * @param timer
    96       *        the {@link Timer} that is used to trigger the scheduled event.
    97       *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
    98       * @param timeoutSeconds
    99       *        read timeout in seconds
    100      */
    101     public ReadTimeoutHandler(Timer timer, int timeoutSeconds) {
    102         this(timer, timeoutSeconds, TimeUnit.SECONDS);
    103     }
    104
    105     /**
    106      * Creates a new instance.
    107      *
    108      * @param timer
    109      *        the {@link Timer} that is used to trigger the scheduled event.
    110      *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
    111      * @param timeout
    112      *        read timeout
    113      * @param unit
    114      *        the {@link TimeUnit} of {@code timeout}
    115      */
    116     public ReadTimeoutHandler(Timer timer, long timeout, TimeUnit unit) {
    117         if (timer == null) {
    118             throw new NullPointerException("timer");
    119         }
    120         if (unit == null) {
    121             throw new NullPointerException("unit");
    122         }
    123
    124         this.timer = timer;
    125         if (timeout <= 0) {
    126             timeoutMillis = 0;
    127         } else {
    128             timeoutMillis = Math.max(unit.toMillis(timeout), 1);
    129         }
    130     }
    131
    132     /**
    133      * Stops the {@link Timer} which was specified in the constructor of this
    134      * handler.  You should not call this method if the {@link Timer} is in use
    135      * by other objects.
    136      */
    137     public void releaseExternalResources() {
    138         timer.stop();
    139     }
    140
    141     public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
    142         if (ctx.getPipeline().isAttached()) {
    143             // channelOpen event has been fired already, which means
    144             // this.channelOpen() will not be invoked.
    145             // We have to initialize here instead.
    146             initialize(ctx);
    147         } else {
    148             // channelOpen event has not been fired yet.
    149             // this.channelOpen() will be invoked and initialization will occur there.
    150         }
    151     }
    152
    153     public void afterAdd(ChannelHandlerContext ctx) throws Exception {
    154         // NOOP
    155     }
    156
    157     public void beforeRemove(ChannelHandlerContext ctx) throws Exception {
    158         destroy();
    159     }
    160
    161     public void afterRemove(ChannelHandlerContext ctx) throws Exception {
    162         // NOOP
    163     }
    164
    165     @Override
    166     public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
    167             throws Exception {
    168         // This method will be invoked only if this handler was added
    169         // before channelOpen event is fired.  If a user adds this handler
    170         // after the channelOpen event, initialize() will be called by beforeAdd().
    171         initialize(ctx);
    172         ctx.sendUpstream(e);
    173     }
    174
    175     @Override
    176     public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
    177             throws Exception {
    178         destroy();
    179         ctx.sendUpstream(e);
    180     }
    181
    182     @Override
    183     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
    184             throws Exception {
    185         updateLastReadTime();
    186         ctx.sendUpstream(e);
    187     }
    188
    189     private void initialize(ChannelHandlerContext ctx) {
    190         updateLastReadTime();
    191         task = new ReadTimeoutTask(ctx);
    192         if (timeoutMillis > 0) {
    193             timeout = timer.newTimeout(task, timeoutMillis, TimeUnit.MILLISECONDS);
    194         }
    195     }
    196
    197     private void updateLastReadTime() {
    198         lastReadTime = System.currentTimeMillis();
    199     }
    200
    201     private void destroy() {
    202         if (timeout != null) {
    203             timeout.cancel();
    204         }
    205         timeout = null;
    206         task = null;
    207     }
    208
    209     protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {
    210         Channels.fireExceptionCaught(ctx, EXCEPTION);
    211     }
    212
    213     private final class ReadTimeoutTask implements TimerTask {
    214
    215         private final ChannelHandlerContext ctx;
    216
    217         ReadTimeoutTask(ChannelHandlerContext ctx) {
    218             this.ctx = ctx;
    219         }
    220
    221         public void run(Timeout timeout) throws Exception {
    222             if (timeout.isCancelled()) {
    223                 return;
    224             }
    225
    226             if (!ctx.getChannel().isOpen()) {
    227                 return;
    228             }
    229
    230             long currentTime = System.currentTimeMillis();
    231             long nextDelay = timeoutMillis - (currentTime - lastReadTime);
    232             if (nextDelay <= 0) {
    233                 // Read timed out - set a new timeout and notify the callback.
    234                 ReadTimeoutHandler.this.timeout =
    235                     timer.newTimeout(this, timeoutMillis, TimeUnit.MILLISECONDS);
    236                 try {
    237                     readTimedOut(ctx);
    238                 } catch (Throwable t) {
    239                     fireExceptionCaught(ctx, t);
    240                 }
    241             } else {
    242                 // Read occurred before the timeout - set a new timeout with shorter delay.
    243                 ReadTimeoutHandler.this.timeout =
    244                     timer.newTimeout(this, nextDelay, TimeUnit.MILLISECONDS);
    245             }
    246         }
    247     }
    248 }