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.ChannelFuture;
24  import org.jboss.netty.channel.ChannelFutureListener;
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.Channels;
29  import org.jboss.netty.channel.MessageEvent;
30  import org.jboss.netty.channel.SimpleChannelDownstreamHandler;
31  import org.jboss.netty.channel.ChannelHandler.Sharable;
32  import org.jboss.netty.util.ExternalResourceReleasable;
33  import org.jboss.netty.util.HashedWheelTimer;
34  import org.jboss.netty.util.Timeout;
35  import org.jboss.netty.util.Timer;
36  import org.jboss.netty.util.TimerTask;
37  
38  /**
39   * Raises a {@link WriteTimeoutException} when no data was written within a
40   * certain period of time.
41   *
42   * <pre>
43   * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
44   *
45   *     private final {@link Timer} timer;
46   *
47   *     public MyPipelineFactory({@link Timer} timer) {
48   *         this.timer = timer;
49   *     }
50   *
51   *     public {@link ChannelPipeline} getPipeline() {
52   *         // An example configuration that implements 30-second write timeout:
53   *         return {@link Channels}.pipeline(
54   *             <b>new {@link WriteTimeoutHandler}(timer, 30), // timer must be shared.</b>
55   *             new MyHandler());
56   *     }
57   * }
58   *
59   * {@link ServerBootstrap} bootstrap = ...;
60   * {@link Timer} timer = new {@link HashedWheelTimer}();
61   * ...
62   * bootstrap.setPipelineFactory(new MyPipelineFactory(timer));
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 ReadTimeoutHandler
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  @Sharable
81  public class WriteTimeoutHandler extends SimpleChannelDownstreamHandler
82                                   implements ExternalResourceReleasable {
83  
84      static final WriteTimeoutException EXCEPTION = new WriteTimeoutException();
85  
86      private final Timer timer;
87      private final long timeoutMillis;
88  
89      /**
90       * Creates a new instance.
91       *
92       * @param timer
93       *        the {@link Timer} that is used to trigger the scheduled event.
94       *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
95       * @param timeoutSeconds
96       *        write timeout in seconds
97       */
98      public WriteTimeoutHandler(Timer timer, int timeoutSeconds) {
99          this(timer, timeoutSeconds, TimeUnit.SECONDS);
100     }
101 
102     /**
103      * Creates a new instance.
104      *
105      * @param timer
106      *        the {@link Timer} that is used to trigger the scheduled event.
107      *        The recommended {@link Timer} implementation is {@link HashedWheelTimer}.
108      * @param timeout
109      *        write timeout
110      * @param unit
111      *        the {@link TimeUnit} of {@code timeout}
112      */
113     public WriteTimeoutHandler(Timer timer, long timeout, TimeUnit unit) {
114         if (timer == null) {
115             throw new NullPointerException("timer");
116         }
117         if (unit == null) {
118             throw new NullPointerException("unit");
119         }
120 
121         this.timer = timer;
122         if (timeout <= 0) {
123             timeoutMillis = 0;
124         } else {
125             timeoutMillis = Math.max(unit.toMillis(timeout), 1);
126         }
127     }
128 
129     /**
130      * Stops the {@link Timer} which was specified in the constructor of this
131      * handler.  You should not call this method if the {@link Timer} is in use
132      * by other objects.
133      */
134     public void releaseExternalResources() {
135         timer.stop();
136     }
137 
138     protected long getTimeoutMillis(MessageEvent e) {
139         return timeoutMillis;
140     }
141 
142     @Override
143     public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
144             throws Exception {
145 
146         long timeoutMillis = getTimeoutMillis(e);
147         if (timeoutMillis > 0) {
148             // Set timeout only when getTimeoutMillis() returns a positive value.
149             ChannelFuture future = e.getFuture();
150             final Timeout timeout = timer.newTimeout(
151                     new WriteTimeoutTask(ctx, future),
152                     timeoutMillis, TimeUnit.MILLISECONDS);
153 
154             future.addListener(new TimeoutCanceller(timeout));
155         }
156 
157         super.writeRequested(ctx, e);
158     }
159 
160     protected void writeTimedOut(ChannelHandlerContext ctx) throws Exception {
161         Channels.fireExceptionCaught(ctx, EXCEPTION);
162     }
163 
164     private final class WriteTimeoutTask implements TimerTask {
165 
166         private final ChannelHandlerContext ctx;
167         private final ChannelFuture future;
168 
169         WriteTimeoutTask(ChannelHandlerContext ctx, ChannelFuture future) {
170             this.ctx = ctx;
171             this.future = future;
172         }
173 
174         public void run(Timeout timeout) throws Exception {
175             if (timeout.isCancelled()) {
176                 return;
177             }
178 
179             if (!ctx.getChannel().isOpen()) {
180                 return;
181             }
182 
183             // Mark the future as failure
184             if (future.setFailure(EXCEPTION)) {
185                 // If succeeded to mark as failure, notify the pipeline, too.
186                 try {
187                     writeTimedOut(ctx);
188                 } catch (Throwable t) {
189                     fireExceptionCaught(ctx, t);
190                 }
191             }
192         }
193     }
194 
195     /**
196      * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
197      * @author <a href="http://gleamynode.net/">Trustin Lee</a>
198      * @version $Rev: 2222 $, $Date: 2010-03-24 14:07:27 +0900 (Wed, 24 Mar 2010) $
199      */
200     private static final class TimeoutCanceller implements ChannelFutureListener {
201         private final Timeout timeout;
202 
203         TimeoutCanceller(Timeout timeout) {
204             this.timeout = timeout;
205         }
206 
207         public void operationComplete(ChannelFuture future) throws Exception {
208             timeout.cancel();
209         }
210     }
211 }