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    *   https://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.Channel.Unsafe;
21  import io.netty.channel.ChannelDuplexHandler;
22  import io.netty.channel.ChannelFutureListener;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelInitializer;
25  import io.netty.channel.ChannelOutboundBuffer;
26  import io.netty.channel.ChannelPromise;
27  import io.netty.util.concurrent.Future;
28  import io.netty.util.concurrent.Ticker;
29  import io.netty.util.internal.ObjectUtil;
30  
31  import java.util.concurrent.TimeUnit;
32  
33  /**
34   * Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed
35   * read, write, or both operation for a while.
36   *
37   * <h3>Supported idle states</h3>
38   * <table border="1">
39   * <tr>
40   * <th>Property</th><th>Meaning</th>
41   * </tr>
42   * <tr>
43   * <td>{@code readerIdleTime}</td>
44   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
45   *     will be triggered when no read was performed for the specified period of
46   *     time.  Specify {@code 0} to disable.</td>
47   * </tr>
48   * <tr>
49   * <td>{@code writerIdleTime}</td>
50   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
51   *     will be triggered when no write was performed for the specified period of
52   *     time.  Specify {@code 0} to disable.</td>
53   * </tr>
54   * <tr>
55   * <td>{@code allIdleTime}</td>
56   * <td>an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
57   *     will be triggered when neither read nor write was performed for the
58   *     specified period of time.  Specify {@code 0} to disable.</td>
59   * </tr>
60   * </table>
61   *
62   * <pre>
63   * // An example that sends a ping message when there is no outbound traffic
64   * // for 30 seconds.  The connection is closed when there is no inbound traffic
65   * // for 60 seconds.
66   *
67   * public class MyChannelInitializer extends {@link ChannelInitializer}&lt;{@link Channel}&gt; {
68   *     {@code @Override}
69   *     public void initChannel({@link Channel} channel) {
70   *         channel.pipeline().addLast("idleStateHandler", new {@link IdleStateHandler}(60, 30, 0));
71   *         channel.pipeline().addLast("myHandler", new MyHandler());
72   *     }
73   * }
74   *
75   * // Handler should handle the {@link IdleStateEvent} triggered by {@link IdleStateHandler}.
76   * public class MyHandler extends {@link ChannelDuplexHandler} {
77   *     {@code @Override}
78   *     public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
79   *         if (evt instanceof {@link IdleStateEvent}) {
80   *             {@link IdleStateEvent} e = ({@link IdleStateEvent}) evt;
81   *             if (e.state() == {@link IdleState}.READER_IDLE) {
82   *                 ctx.close();
83   *             } else if (e.state() == {@link IdleState}.WRITER_IDLE) {
84   *                 ctx.writeAndFlush(new PingMessage());
85   *             }
86   *         }
87   *     }
88   * }
89   *
90   * {@link ServerBootstrap} bootstrap = ...;
91   * ...
92   * bootstrap.childHandler(new MyChannelInitializer());
93   * ...
94   * </pre>
95   *
96   * @see ReadTimeoutHandler
97   * @see WriteTimeoutHandler
98   */
99  public class IdleStateHandler extends ChannelDuplexHandler {
100     private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
101 
102     private final boolean observeOutput;
103     private final long readerIdleTimeNanos;
104     private final long writerIdleTimeNanos;
105     private final long allIdleTimeNanos;
106 
107     private Ticker ticker = Ticker.systemTicker();
108 
109     private Future<?> readerIdleTimeout;
110     private long lastReadTime;
111     private boolean firstReaderIdleEvent = true;
112 
113     private Future<?> writerIdleTimeout;
114     private long lastWriteTime;
115     private boolean firstWriterIdleEvent = true;
116 
117     private Future<?> allIdleTimeout;
118     private boolean firstAllIdleEvent = true;
119 
120     private byte state;
121     private static final byte ST_INITIALIZED = 1;
122     private static final byte ST_DESTROYED = 2;
123 
124     private boolean reading;
125 
126     private long lastChangeCheckTimeStamp;
127     private int lastMessageHashCode;
128     private long lastPendingWriteBytes;
129     private long lastFlushProgress;
130     // Not create a new ChannelFutureListener per write operation to reduce GC pressure.
131     private final ChannelFutureListener writeListener = future -> {
132         lastWriteTime = ticker.nanoTime();
133         firstWriterIdleEvent = firstAllIdleEvent = true;
134     };
135 
136     /**
137      * Creates a new instance firing {@link IdleStateEvent}s.
138      *
139      * @param readerIdleTimeSeconds
140      *        an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE}
141      *        will be triggered when no read was performed for the specified
142      *        period of time.  Specify {@code 0} to disable.
143      * @param writerIdleTimeSeconds
144      *        an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE}
145      *        will be triggered when no write was performed for the specified
146      *        period of time.  Specify {@code 0} to disable.
147      * @param allIdleTimeSeconds
148      *        an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE}
149      *        will be triggered when neither read nor write was performed for
150      *        the specified period of time.  Specify {@code 0} to disable.
151      */
152     public IdleStateHandler(
153             int readerIdleTimeSeconds,
154             int writerIdleTimeSeconds,
155             int allIdleTimeSeconds) {
156 
157         this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
158              TimeUnit.SECONDS);
159     }
160 
161     /**
162      * @see #IdleStateHandler(boolean, long, long, long, TimeUnit)
163      */
164     public IdleStateHandler(
165             long readerIdleTime, long writerIdleTime, long allIdleTime,
166             TimeUnit unit) {
167         this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
168     }
169 
170     /**
171      * Creates a new instance firing {@link IdleStateEvent}s.
172      *
173      * @param observeOutput
174      *        whether or not the consumption of {@code bytes} should be taken into
175      *        consideration when assessing write idleness. The default is {@code false}.
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(boolean observeOutput,
193             long readerIdleTime, long writerIdleTime, long allIdleTime,
194             TimeUnit unit) {
195         ObjectUtil.checkNotNull(unit, "unit");
196 
197         this.observeOutput = observeOutput;
198 
199         if (readerIdleTime <= 0) {
200             readerIdleTimeNanos = 0;
201         } else {
202             readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
203         }
204         if (writerIdleTime <= 0) {
205             writerIdleTimeNanos = 0;
206         } else {
207             writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
208         }
209         if (allIdleTime <= 0) {
210             allIdleTimeNanos = 0;
211         } else {
212             allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
213         }
214     }
215 
216     /**
217      * Return the readerIdleTime that was given when instance this class in milliseconds.
218      *
219      */
220     public long getReaderIdleTimeInMillis() {
221         return TimeUnit.NANOSECONDS.toMillis(readerIdleTimeNanos);
222     }
223 
224     /**
225      * Return the writerIdleTime that was given when instance this class in milliseconds.
226      *
227      */
228     public long getWriterIdleTimeInMillis() {
229         return TimeUnit.NANOSECONDS.toMillis(writerIdleTimeNanos);
230     }
231 
232     /**
233      * Return the allIdleTime that was given when instance this class in milliseconds.
234      *
235      */
236     public long getAllIdleTimeInMillis() {
237         return TimeUnit.NANOSECONDS.toMillis(allIdleTimeNanos);
238     }
239 
240     @Override
241     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
242         this.ticker = ctx.executor().ticker();
243         if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
244             // channelActive() event has been fired already, which means this.channelActive() will
245             // not be invoked. We have to initialize here instead.
246             initialize(ctx);
247         } else {
248             // channelActive() event has not been fired yet.  this.channelActive() will be invoked
249             // and initialization will occur there.
250         }
251     }
252 
253     @Override
254     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
255         destroy();
256     }
257 
258     @Override
259     public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
260         // Initialize early if channel is active already.
261         if (ctx.channel().isActive()) {
262             initialize(ctx);
263         }
264         super.channelRegistered(ctx);
265     }
266 
267     @Override
268     public void channelActive(ChannelHandlerContext ctx) throws Exception {
269         // This method will be invoked only if this handler was added
270         // before channelActive() event is fired.  If a user adds this handler
271         // after the channelActive() event, initialize() will be called by beforeAdd().
272         initialize(ctx);
273         super.channelActive(ctx);
274     }
275 
276     @Override
277     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
278         destroy();
279         super.channelInactive(ctx);
280     }
281 
282     @Override
283     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
284         if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
285             reading = true;
286             firstReaderIdleEvent = firstAllIdleEvent = true;
287         }
288         ctx.fireChannelRead(msg);
289     }
290 
291     @Override
292     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
293         if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
294             lastReadTime = ticker.nanoTime();
295             reading = false;
296         }
297         ctx.fireChannelReadComplete();
298     }
299 
300     @Override
301     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
302         // Allow writing with void promise if handler is only configured for read timeout events.
303         if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
304             ctx.write(msg, promise.unvoid()).addListener(writeListener);
305         } else {
306             ctx.write(msg, promise);
307         }
308     }
309 
310     /**
311      * Reset the read timeout. As this handler is not thread-safe, this method <b>must</b> be called on the event loop.
312      */
313     public void resetReadTimeout() {
314         if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
315             lastReadTime = ticker.nanoTime();
316             reading = false;
317             firstReaderIdleEvent = firstAllIdleEvent = true;
318         }
319     }
320 
321     /**
322      * Reset the write timeout. As this handler is not thread-safe, this method <b>must</b> be called on the event loop.
323      */
324     public void resetWriteTimeout() {
325         if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
326             lastWriteTime = ticker.nanoTime();
327             firstWriterIdleEvent = firstAllIdleEvent = true;
328         }
329     }
330 
331     private void initialize(ChannelHandlerContext ctx) {
332         // Avoid the case where destroy() is called before scheduling timeouts.
333         // See: https://github.com/netty/netty/issues/143
334         switch (state) {
335         case 1:
336         case 2:
337             return;
338         default:
339              break;
340         }
341 
342         state = ST_INITIALIZED;
343         initOutputChanged(ctx);
344 
345         lastReadTime = lastWriteTime = ticker.nanoTime();
346         if (readerIdleTimeNanos > 0) {
347             readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
348                     readerIdleTimeNanos, TimeUnit.NANOSECONDS);
349         }
350         if (writerIdleTimeNanos > 0) {
351             writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
352                     writerIdleTimeNanos, TimeUnit.NANOSECONDS);
353         }
354         if (allIdleTimeNanos > 0) {
355             allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
356                     allIdleTimeNanos, TimeUnit.NANOSECONDS);
357         }
358     }
359 
360     /**
361      * This method is visible for testing!
362      */
363     Future<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
364         return ctx.executor().schedule(task, delay, unit);
365     }
366 
367     private void destroy() {
368         state = ST_DESTROYED;
369 
370         if (readerIdleTimeout != null) {
371             readerIdleTimeout.cancel(false);
372             readerIdleTimeout = null;
373         }
374         if (writerIdleTimeout != null) {
375             writerIdleTimeout.cancel(false);
376             writerIdleTimeout = null;
377         }
378         if (allIdleTimeout != null) {
379             allIdleTimeout.cancel(false);
380             allIdleTimeout = null;
381         }
382     }
383 
384     /**
385      * Is called when an {@link IdleStateEvent} should be fired. This implementation calls
386      * {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
387      */
388     protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
389         ctx.fireUserEventTriggered(evt);
390     }
391 
392     /**
393      * Returns a {@link IdleStateEvent}.
394      */
395     protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
396         switch (state) {
397             case ALL_IDLE:
398                 return first ? IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT : IdleStateEvent.ALL_IDLE_STATE_EVENT;
399             case READER_IDLE:
400                 return first ? IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT : IdleStateEvent.READER_IDLE_STATE_EVENT;
401             case WRITER_IDLE:
402                 return first ? IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT : IdleStateEvent.WRITER_IDLE_STATE_EVENT;
403             default:
404                 throw new IllegalArgumentException("Unhandled: state=" + state + ", first=" + first);
405         }
406     }
407 
408     /**
409      * @see #hasOutputChanged(ChannelHandlerContext, boolean)
410      */
411     private void initOutputChanged(ChannelHandlerContext ctx) {
412         if (observeOutput) {
413             Channel channel = ctx.channel();
414             Unsafe unsafe = channel.unsafe();
415             ChannelOutboundBuffer buf = unsafe.outboundBuffer();
416 
417             if (buf != null) {
418                 lastMessageHashCode = System.identityHashCode(buf.current());
419                 lastPendingWriteBytes = buf.totalPendingWriteBytes();
420                 lastFlushProgress = buf.currentProgress();
421             }
422         }
423     }
424 
425     /**
426      * Returns {@code true} if and only if the {@link IdleStateHandler} was constructed
427      * with {@link #observeOutput} enabled and there has been an observed change in the
428      * {@link ChannelOutboundBuffer} between two consecutive calls of this method.
429      *
430      * https://github.com/netty/netty/issues/6150
431      */
432     private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
433         if (observeOutput) {
434 
435             // We can take this shortcut if the ChannelPromises that got passed into write()
436             // appear to complete. It indicates "change" on message level and we simply assume
437             // that there's change happening on byte level. If the user doesn't observe channel
438             // writability events then they'll eventually OOME and there's clearly a different
439             // problem and idleness is least of their concerns.
440             if (lastChangeCheckTimeStamp != lastWriteTime) {
441                 lastChangeCheckTimeStamp = lastWriteTime;
442 
443                 // But this applies only if it's the non-first call.
444                 if (!first) {
445                     return true;
446                 }
447             }
448 
449             Channel channel = ctx.channel();
450             Unsafe unsafe = channel.unsafe();
451             ChannelOutboundBuffer buf = unsafe.outboundBuffer();
452 
453             if (buf != null) {
454                 int messageHashCode = System.identityHashCode(buf.current());
455                 long pendingWriteBytes = buf.totalPendingWriteBytes();
456 
457                 if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
458                     lastMessageHashCode = messageHashCode;
459                     lastPendingWriteBytes = pendingWriteBytes;
460 
461                     if (!first) {
462                         return true;
463                     }
464                 }
465 
466                 long flushProgress = buf.currentProgress();
467                 if (flushProgress != lastFlushProgress) {
468                     lastFlushProgress = flushProgress;
469                     return !first;
470                 }
471             }
472         }
473 
474         return false;
475     }
476 
477     private abstract static class AbstractIdleTask implements Runnable {
478 
479         private final ChannelHandlerContext ctx;
480 
481         AbstractIdleTask(ChannelHandlerContext ctx) {
482             this.ctx = ctx;
483         }
484 
485         @Override
486         public void run() {
487             if (!ctx.channel().isOpen()) {
488                 return;
489             }
490 
491             run(ctx);
492         }
493 
494         protected abstract void run(ChannelHandlerContext ctx);
495     }
496 
497     private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
498 
499         ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
500             super(ctx);
501         }
502 
503         @Override
504         protected void run(ChannelHandlerContext ctx) {
505             long nextDelay = readerIdleTimeNanos;
506             if (!reading) {
507                 nextDelay -= ticker.nanoTime() - lastReadTime;
508             }
509 
510             if (nextDelay <= 0) {
511                 // Reader is idle - set a new timeout and notify the callback.
512                 readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
513 
514                 boolean first = firstReaderIdleEvent;
515                 firstReaderIdleEvent = false;
516 
517                 try {
518                     IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
519                     channelIdle(ctx, event);
520                 } catch (Throwable t) {
521                     ctx.fireExceptionCaught(t);
522                 }
523             } else {
524                 // Read occurred before the timeout - set a new timeout with shorter delay.
525                 readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
526             }
527         }
528     }
529 
530     private final class WriterIdleTimeoutTask extends AbstractIdleTask {
531 
532         WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
533             super(ctx);
534         }
535 
536         @Override
537         protected void run(ChannelHandlerContext ctx) {
538 
539             long lastWriteTime = IdleStateHandler.this.lastWriteTime;
540             long nextDelay = writerIdleTimeNanos - (ticker.nanoTime() - lastWriteTime);
541             if (nextDelay <= 0) {
542                 // Writer is idle - set a new timeout and notify the callback.
543                 writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
544 
545                 boolean first = firstWriterIdleEvent;
546                 firstWriterIdleEvent = false;
547 
548                 try {
549                     if (hasOutputChanged(ctx, first)) {
550                         return;
551                     }
552 
553                     IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
554                     channelIdle(ctx, event);
555                 } catch (Throwable t) {
556                     ctx.fireExceptionCaught(t);
557                 }
558             } else {
559                 // Write occurred before the timeout - set a new timeout with shorter delay.
560                 writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
561             }
562         }
563     }
564 
565     private final class AllIdleTimeoutTask extends AbstractIdleTask {
566 
567         AllIdleTimeoutTask(ChannelHandlerContext ctx) {
568             super(ctx);
569         }
570 
571         @Override
572         protected void run(ChannelHandlerContext ctx) {
573 
574             long nextDelay = allIdleTimeNanos;
575             if (!reading) {
576                 nextDelay -= ticker.nanoTime() - Math.max(lastReadTime, lastWriteTime);
577             }
578             if (nextDelay <= 0) {
579                 // Both reader and writer are idle - set a new timeout and
580                 // notify the callback.
581                 allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);
582 
583                 boolean first = firstAllIdleEvent;
584                 firstAllIdleEvent = false;
585 
586                 try {
587                     if (hasOutputChanged(ctx, first)) {
588                         return;
589                     }
590 
591                     IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
592                     channelIdle(ctx, event);
593                 } catch (Throwable t) {
594                     ctx.fireExceptionCaught(t);
595                 }
596             } else {
597                 // Either read or write occurred before the timeout - set a new
598                 // timeout with shorter delay.
599                 allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
600             }
601         }
602     }
603 }