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 io.netty.handler.timeout;
17  
18  import io.netty.bootstrap.ServerBootstrap;
19  import io.netty.channel.Channel;
20  import io.netty.channel.ChannelFuture;
21  import io.netty.channel.ChannelFutureListener;
22  import io.netty.channel.ChannelHandlerAdapter;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelInitializer;
25  import io.netty.channel.ChannelPromise;
26  import io.netty.util.concurrent.EventExecutor;
27  
28  import java.util.concurrent.ScheduledFuture;
29  import java.util.concurrent.TimeUnit;
30  
31  /**
32   * Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
33   * read, write, or both operation for a while.
34   *
35   * <h3>Supported idle states</h3>
36   * <table border="1">
37   * <tr>
38   * <th>Property</th><th>Meaning</th>
39   * </tr>
40   * <tr>
41   * <td>{@code readerIdleTime}</td>
42   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
43   *     will be triggered when no read was performed for the specified period of
44   *     time.  Specify {@code 0} to disable.</td>
45   * </tr>
46   * <tr>
47   * <td>{@code writerIdleTime}</td>
48   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
49   *     will be triggered when no write was performed for the specified period of
50   *     time.  Specify {@code 0} to disable.</td>
51   * </tr>
52   * <tr>
53   * <td>{@code allIdleTime}</td>
54   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
55   *     will be triggered when neither read nor write was performed for the
56   *     specified period of time.  Specify {@code 0} to disable.</td>
57   * </tr>
58   * </table>
59   *
60   * <pre>
61   * // An example that sends a ping message when there is no outbound traffic
62   * // for 30 seconds.  The connection is closed when there is no inbound traffic
63   * // for 60 seconds.
64   *
65   * public class MyChannelInitializer extends {@link ChannelInitializer}&lt;{@link Channel}&gt; {
66   *     {@code @Override}
67   *     public void initChannel({@link Channel} channel) {
68   *         channel.pipeline().addLast("idleStateHandler", new {@link IdleStateHandler}(60, 30, 0));
69   *         channel.pipeline().addLast("myHandler", new MyHandler());
70   *     }
71   * }
72   *
73   * // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
74   * public class MyHandler extends {@link ChannelHandlerAdapter} {
75   *     {@code @Override}
76   *     public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
77   *         if (evt instanceof {@link IdleStateEvent}) {
78   *             {@link IdleStateEvent} e = ({@link IdleStateEvent}) evt;
79   *             if (e.state() == {@link IdleState}.READER_IDLE) {
80   *                 ctx.close();
81   *             } else if (e.state() == {@link IdleState}.WRITER_IDLE) {
82   *                 ctx.writeAndFlush(new PingMessage());
83   *             }
84   *         }
85   *     }
86   * }
87   *
88   * {@link ServerBootstrap} bootstrap = ...;
89   * ...
90   * bootstrap.childHandler(new MyChannelInitializer());
91   * ...
92   * </pre>
93   *
94   * @see ReadTimeoutHandler
95   * @see WriteTimeoutHandler
96   */
97  public class IdleStateHandler extends ChannelHandlerAdapter {
98      private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
99  
100     private final long readerIdleTimeNanos;
101     private final long writerIdleTimeNanos;
102     private final long allIdleTimeNanos;
103 
104     volatile ScheduledFuture<?> readerIdleTimeout;
105     volatile long lastReadTime;
106     private boolean firstReaderIdleEvent = true;
107 
108     volatile ScheduledFuture<?> writerIdleTimeout;
109     volatile long lastWriteTime;
110     private boolean firstWriterIdleEvent = true;
111 
112     volatile ScheduledFuture<?> allIdleTimeout;
113     private boolean firstAllIdleEvent = true;
114 
115     private volatile int state; // 0 - none, 1 - initialized, 2 - destroyed
116 
117     /**
118      * Creates a new instance firing {@link IdleStateEvent}s.
119      *
120      * @param readerIdleTimeSeconds
121      *        an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
122      *        will be triggered when no read was performed for the specified
123      *        period of time.  Specify {@code 0} to disable.
124      * @param writerIdleTimeSeconds
125      *        an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
126      *        will be triggered when no write was performed for the specified
127      *        period of time.  Specify {@code 0} to disable.
128      * @param allIdleTimeSeconds
129      *        an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
130      *        will be triggered when neither read nor write was performed for
131      *        the specified period of time.  Specify {@code 0} to disable.
132      */
133     public IdleStateHandler(
134             int readerIdleTimeSeconds,
135             int writerIdleTimeSeconds,
136             int allIdleTimeSeconds) {
137 
138         this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
139              TimeUnit.SECONDS);
140     }
141 
142     /**
143      * Creates a new instance firing {@link IdleStateEvent}s.
144      *
145      * @param readerIdleTime
146      *        an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
147      *        will be triggered when no read was performed for the specified
148      *        period of time.  Specify {@code 0} to disable.
149      * @param writerIdleTime
150      *        an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
151      *        will be triggered when no write was performed for the specified
152      *        period of time.  Specify {@code 0} to disable.
153      * @param allIdleTime
154      *        an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
155      *        will be triggered when neither read nor write was performed for
156      *        the specified period of time.  Specify {@code 0} to disable.
157      * @param unit
158      *        the {@link TimeUnit} of {@code readerIdleTime},
159      *        {@code writeIdleTime}, and {@code allIdleTime}
160      */
161     public IdleStateHandler(
162             long readerIdleTime, long writerIdleTime, long allIdleTime,
163             TimeUnit unit) {
164         if (unit == null) {
165             throw new NullPointerException("unit");
166         }
167 
168         if (readerIdleTime <= 0) {
169             readerIdleTimeNanos = 0;
170         } else {
171             readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
172         }
173         if (writerIdleTime <= 0) {
174             writerIdleTimeNanos = 0;
175         } else {
176             writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
177         }
178         if (allIdleTime <= 0) {
179             allIdleTimeNanos = 0;
180         } else {
181             allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
182         }
183     }
184 
185     /**
186      * Return the readerIdleTime that was given when instance this class in milliseconds.
187      *
188      */
189     public long getReaderIdleTimeInMillis() {
190         return TimeUnit.NANOSECONDS.toMillis(readerIdleTimeNanos);
191     }
192 
193     /**
194      * Return the writerIdleTime that was given when instance this class in milliseconds.
195      *
196      */
197     public long getWriterIdleTimeInMillis() {
198         return TimeUnit.NANOSECONDS.toMillis(writerIdleTimeNanos);
199     }
200 
201     /**
202      * Return the allIdleTime that was given when instance this class in milliseconds.
203      *
204      */
205     public long getAllIdleTimeInMillis() {
206         return TimeUnit.NANOSECONDS.toMillis(allIdleTimeNanos);
207     }
208 
209     @Override
210     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
211         if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
212             // channelActvie() event has been fired already, which means this.channelActive() will
213             // not be invoked. We have to initialize here instead.
214             initialize(ctx);
215         } else {
216             // channelActive() event has not been fired yet.  this.channelActive() will be invoked
217             // and initialization will occur there.
218         }
219     }
220 
221     @Override
222     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
223         destroy();
224     }
225 
226     @Override
227     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
228         // Initialize early if channel is active already.
229         if (ctx.channel().isActive()) {
230             initialize(ctx);
231         }
232         super.channelRegistered(ctx);
233     }
234 
235     @Override
236     public void channelActive(ChannelHandlerContext ctx) throws Exception {
237         // This method will be invoked only if this handler was added
238         // before channelActive() event is fired.  If a user adds this handler
239         // after the channelActive() event, initialize() will be called by beforeAdd().
240         initialize(ctx);
241         super.channelActive(ctx);
242     }
243 
244     @Override
245     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
246         destroy();
247         super.channelInactive(ctx);
248     }
249 
250     @Override
251     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
252         lastReadTime = System.nanoTime();
253         firstReaderIdleEvent = firstAllIdleEvent = true;
254         ctx.fireChannelRead(msg);
255     }
256 
257     @Override
258     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
259         ChannelPromise unvoid = promise.unvoid();
260         unvoid.addListener(new ChannelFutureListener() {
261             @Override
262             public void operationComplete(ChannelFuture future) throws Exception {
263                 lastWriteTime = System.nanoTime();
264                 firstWriterIdleEvent = firstAllIdleEvent = true;
265             }
266         });
267         ctx.write(msg, unvoid);
268     }
269 
270     private void initialize(ChannelHandlerContext ctx) {
271         // Avoid the case where destroy() is called before scheduling timeouts.
272         // See: https://github.com/netty/netty/issues/143
273         switch (state) {
274         case 1:
275         case 2:
276             return;
277         }
278 
279         state = 1;
280 
281         EventExecutor loop = ctx.executor();
282 
283         lastReadTime = lastWriteTime = System.nanoTime();
284         if (readerIdleTimeNanos > 0) {
285             readerIdleTimeout = loop.schedule(
286                     new ReaderIdleTimeoutTask(ctx),
287                     readerIdleTimeNanos, TimeUnit.NANOSECONDS);
288         }
289         if (writerIdleTimeNanos > 0) {
290             writerIdleTimeout = loop.schedule(
291                     new WriterIdleTimeoutTask(ctx),
292                     writerIdleTimeNanos, TimeUnit.NANOSECONDS);
293         }
294         if (allIdleTimeNanos > 0) {
295             allIdleTimeout = loop.schedule(
296                     new AllIdleTimeoutTask(ctx),
297                     allIdleTimeNanos, TimeUnit.NANOSECONDS);
298         }
299     }
300 
301     private void destroy() {
302         state = 2;
303 
304         if (readerIdleTimeout != null) {
305             readerIdleTimeout.cancel(false);
306             readerIdleTimeout = null;
307         }
308         if (writerIdleTimeout != null) {
309             writerIdleTimeout.cancel(false);
310             writerIdleTimeout = null;
311         }
312         if (allIdleTimeout != null) {
313             allIdleTimeout.cancel(false);
314             allIdleTimeout = null;
315         }
316     }
317 
318     /**
319      * Is called when an {@link IdleStateEvent} should be fired. This implementation calls
320      * {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
321      */
322     protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
323         ctx.fireUserEventTriggered(evt);
324     }
325 
326     private final class ReaderIdleTimeoutTask implements Runnable {
327 
328         private final ChannelHandlerContext ctx;
329 
330         ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
331             this.ctx = ctx;
332         }
333 
334         @Override
335         public void run() {
336             if (!ctx.channel().isOpen()) {
337                 return;
338             }
339 
340             long currentTime = System.nanoTime();
341             long lastReadTime = IdleStateHandler.this.lastReadTime;
342             long nextDelay = readerIdleTimeNanos - (currentTime - lastReadTime);
343             if (nextDelay <= 0) {
344                 // Reader is idle - set a new timeout and notify the callback.
345                 readerIdleTimeout =
346                     ctx.executor().schedule(this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
347                 try {
348                     IdleStateEvent event;
349                     if (firstReaderIdleEvent) {
350                         firstReaderIdleEvent = false;
351                         event = IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT;
352                     } else {
353                         event = IdleStateEvent.READER_IDLE_STATE_EVENT;
354                     }
355                     channelIdle(ctx, event);
356                 } catch (Throwable t) {
357                     ctx.fireExceptionCaught(t);
358                 }
359             } else {
360                 // Read occurred before the timeout - set a new timeout with shorter delay.
361                 readerIdleTimeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
362             }
363         }
364     }
365 
366     private final class WriterIdleTimeoutTask implements Runnable {
367 
368         private final ChannelHandlerContext ctx;
369 
370         WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
371             this.ctx = ctx;
372         }
373 
374         @Override
375         public void run() {
376             if (!ctx.channel().isOpen()) {
377                 return;
378             }
379 
380             long currentTime = System.nanoTime();
381             long lastWriteTime = IdleStateHandler.this.lastWriteTime;
382             long nextDelay = writerIdleTimeNanos - (currentTime - lastWriteTime);
383             if (nextDelay <= 0) {
384                 // Writer is idle - set a new timeout and notify the callback.
385                 writerIdleTimeout = ctx.executor().schedule(
386                         this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
387                 try {
388                     IdleStateEvent event;
389                     if (firstWriterIdleEvent) {
390                         firstWriterIdleEvent = false;
391                         event = IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT;
392                     } else {
393                         event = IdleStateEvent.WRITER_IDLE_STATE_EVENT;
394                     }
395                     channelIdle(ctx, event);
396                 } catch (Throwable t) {
397                     ctx.fireExceptionCaught(t);
398                 }
399             } else {
400                 // Write occurred before the timeout - set a new timeout with shorter delay.
401                 writerIdleTimeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
402             }
403         }
404     }
405 
406     private final class AllIdleTimeoutTask implements Runnable {
407 
408         private final ChannelHandlerContext ctx;
409 
410         AllIdleTimeoutTask(ChannelHandlerContext ctx) {
411             this.ctx = ctx;
412         }
413 
414         @Override
415         public void run() {
416             if (!ctx.channel().isOpen()) {
417                 return;
418             }
419 
420             long currentTime = System.nanoTime();
421             long lastIoTime = Math.max(lastReadTime, lastWriteTime);
422             long nextDelay = allIdleTimeNanos - (currentTime - lastIoTime);
423             if (nextDelay <= 0) {
424                 // Both reader and writer are idle - set a new timeout and
425                 // notify the callback.
426                 allIdleTimeout = ctx.executor().schedule(
427                         this, allIdleTimeNanos, TimeUnit.NANOSECONDS);
428                 try {
429                     IdleStateEvent event;
430                     if (firstAllIdleEvent) {
431                         firstAllIdleEvent = false;
432                         event = IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT;
433                     } else {
434                         event = IdleStateEvent.ALL_IDLE_STATE_EVENT;
435                     }
436                     channelIdle(ctx, event);
437                 } catch (Throwable t) {
438                     ctx.fireExceptionCaught(t);
439                 }
440             } else {
441                 // Either read or write occurred before the timeout - set a new
442                 // timeout with shorter delay.
443                 allIdleTimeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
444             }
445         }
446     }
447 }