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