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 }