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 }