View Javadoc
1   /*
2    * Copyright 2016 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version
5    * 2.0 (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    * 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 under
14   * the License.
15   */
16  package io.netty.handler.flow;
17  
18  import java.util.ArrayDeque;
19  import java.util.Queue;
20  
21  import io.netty.channel.ChannelConfig;
22  import io.netty.channel.ChannelDuplexHandler;
23  import io.netty.channel.ChannelHandler;
24  import io.netty.channel.ChannelHandlerContext;
25  import io.netty.handler.codec.ByteToMessageDecoder;
26  import io.netty.handler.codec.MessageToByteEncoder;
27  import io.netty.util.Recycler;
28  import io.netty.util.ReferenceCountUtil;
29  import io.netty.util.internal.ObjectPool.Handle;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  /**
34   * The {@link FlowControlHandler} ensures that only one message per {@code read()} is sent downstream.
35   * <p>
36   * Classes such as {@link ByteToMessageDecoder} or {@link MessageToByteEncoder} are free to emit as
37   * many events as they like for any given input. A channel's auto reading configuration doesn't usually
38   * apply in these scenarios. This is causing problems in downstream {@link ChannelHandler}s that would
39   * like to hold subsequent events while they're processing one event. It's a common problem with the
40   * {@code HttpObjectDecoder} that will very often fire an {@code HttpRequest} that is immediately followed
41   * by a {@code LastHttpContent} event.
42   *
43   * <pre>{@code
44   * ChannelPipeline pipeline = ...;
45   *
46   * pipeline.addLast(new HttpServerCodec());
47   * pipeline.addLast(new FlowControlHandler());
48   *
49   * pipeline.addLast(new MyExampleHandler());
50   *
51   * class MyExampleHandler extends ChannelInboundHandlerAdapter {
52   *   @Override
53   *   public void channelRead(ChannelHandlerContext ctx, Object msg) {
54   *     if (msg instanceof HttpRequest) {
55   *       ctx.channel().config().setAutoRead(false);
56   *
57   *       // The FlowControlHandler will hold any subsequent events that
58   *       // were emitted by HttpObjectDecoder until auto reading is turned
59   *       // back on or Channel#read() is being called.
60   *     }
61   *   }
62   * }
63   * }</pre>
64   *
65   * @see ChannelConfig#setAutoRead(boolean)
66   */
67  public class FlowControlHandler extends ChannelDuplexHandler {
68      private static final InternalLogger logger = InternalLoggerFactory.getInstance(FlowControlHandler.class);
69  
70      private final boolean releaseMessages;
71  
72      private RecyclableArrayDeque queue;
73  
74      private ChannelConfig config;
75  
76      /**
77       * Number of unsatisfied downstream {@code read()} calls. A downstream {@code read()} is considered unsatisfied
78       * if auto-read is off and if it has not yet been paired with a {@code fireChannelRead} or
79       * a cumulative {@code fireChannelReadComplete}.
80       * <p>
81       * A {@code read()} can be satisfied in three ways, whichever comes first:
82       * <ul>
83       *     <li>inside the {@code read()} call itself, by {@code dequeue()}ing a message</li>
84       *     <li>in a {@code channelRead()}</li>
85       *     <li>in a {@code channelReadComplete()}</li>
86       * </ul>
87       * A {@code read()} can be satisfied with auto-read on.
88       * <p>
89       * When one or more {@code read()} calls are unsatisfied, a downstream {@code channelReadComplete} is fired
90       * only when either of the following happens:
91       * <ul>
92       *     <li>auto-read is off and {@code unsatisfiedReads} returns to zero after {@code dequeue()}ing, or</li>
93       *     <li>an upstream {@code channelReadComplete} arrives</li>
94       * </ul>
95       */
96      private int unsatisfiedReads;
97  
98      /**
99       * {@code true} while a {@link #dequeue(ChannelHandlerContext)} loop is on the stack.
100      */
101     private boolean dequeuing;
102 
103     public FlowControlHandler() {
104         this(true);
105     }
106 
107     public FlowControlHandler(boolean releaseMessages) {
108         this.releaseMessages = releaseMessages;
109     }
110 
111     /**
112      * Determine if the underlying {@link Queue} is empty. This method exists for
113      * testing, debugging and inspection purposes and it is not Thread safe!
114      */
115     boolean isQueueEmpty() {
116         return queue == null || queue.isEmpty();
117     }
118 
119     /**
120      * Releases all messages and destroys the {@link Queue}.
121      */
122     private void destroy() {
123         if (queue != null) {
124 
125             if (!queue.isEmpty()) {
126                 logger.trace("Non-empty queue: {}", queue);
127 
128                 if (releaseMessages) {
129                     Object msg;
130                     while ((msg = queue.poll()) != null) {
131                         ReferenceCountUtil.safeRelease(msg);
132                     }
133                 }
134             }
135 
136             queue.recycle();
137             queue = null;
138         }
139     }
140 
141     @Override
142     public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
143         config = ctx.channel().config();
144     }
145 
146     @Override
147     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
148         super.handlerRemoved(ctx);
149         if (!isQueueEmpty()) {
150             unsatisfiedReads = queue.size();
151             dequeue(ctx);
152             ctx.fireChannelReadComplete();
153         }
154         destroy();
155     }
156 
157     @Override
158     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
159         destroy();
160         ctx.fireChannelInactive();
161     }
162 
163     @Override
164     public void read(ChannelHandlerContext ctx) throws Exception {
165         if (!config.isAutoRead()) {
166             unsatisfiedReads++;
167         }
168 
169         boolean didSatisfyARead = dequeue(ctx);
170         boolean isAutoRead = config.isAutoRead();
171         if (!didSatisfyARead || isAutoRead) {
172             assert unsatisfiedReads > 0 || isAutoRead;
173             // We either could not satisfy the read or auto-read is on.
174             // In both cases we need to delegate the read upstream.
175             ctx.read();
176         } else if (unsatisfiedReads == 0 && !dequeuing) {
177             // Auto-read is off, and we have satisfied all reads.
178             // As such, we can complete the current read cycle. && !dequeueing makes sure we are completing the
179             // read cycle only once in the top-most read() call.
180             ctx.fireChannelReadComplete();
181         } else {
182             // Auto-read is off, and either reads are still unsatisfied or we are nested in a dequeue.
183             // Wait for the outermost call, an upstream channelRead() or a channelReadComplete().
184         }
185     }
186 
187     @Override
188     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
189         if (queue == null) {
190             queue = RecyclableArrayDeque.newInstance();
191         }
192 
193         queue.offer(msg);
194 
195         if (dequeue(ctx)) {
196             if (!config.isAutoRead() && unsatisfiedReads == 0 && !dequeuing) {
197                 ctx.fireChannelReadComplete();
198             }
199         }
200     }
201 
202     @Override
203     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
204         // Upstream closed the read cycle. Collapse every outstanding read() into a single downstream
205         // channelReadComplete; spurious upstream completions with no pending read are dropped.
206         if (config.isAutoRead() || unsatisfiedReads > 0) {
207             unsatisfiedReads = 0;
208             ctx.fireChannelReadComplete();
209         }
210     }
211 
212     /**
213      * Dequeues messages while auto-read is enabled or downstream reads are unsatisfied, and updates
214      * {@code unsatisfiedReads} accordingly.
215      *
216      * @see #read(ChannelHandlerContext)
217      * @see #channelRead(ChannelHandlerContext, Object)
218      */
219     private boolean dequeue(ChannelHandlerContext ctx) {
220         boolean didSatisfyARead = false;
221 
222         boolean wasDequeuing = dequeuing;
223         dequeuing = true;
224         try {
225             // fireChannelRead(...) may call ctx.read() and so this method may be re-entered. Because of that
226             // we need to check if queue was set to null in the meantime and, if so, break out of the loop.
227             while (queue != null && (config.isAutoRead() || unsatisfiedReads > 0)) {
228                 Object msg = queue.poll();
229                 if (msg == null) {
230                     break;
231                 }
232 
233                 if (unsatisfiedReads > 0) {
234                     unsatisfiedReads--;
235                 }
236                 ctx.fireChannelRead(msg);
237 
238                 didSatisfyARead = true;
239             }
240 
241             if (queue != null && queue.isEmpty()) {
242                 queue.recycle();
243                 queue = null;
244             }
245 
246             return didSatisfyARead;
247         } finally {
248             dequeuing = wasDequeuing;
249         }
250     }
251 
252     /**
253      * A recyclable {@link ArrayDeque}.
254      */
255     private static final class RecyclableArrayDeque extends ArrayDeque<Object> {
256 
257         private static final long serialVersionUID = 0L;
258 
259         /**
260          * A value of {@code 2} should be a good choice for most scenarios.
261          */
262         private static final int DEFAULT_NUM_ELEMENTS = 2;
263 
264         private static final Recycler<RecyclableArrayDeque> RECYCLER =
265                 new Recycler<RecyclableArrayDeque>() {
266                     @Override
267                     protected RecyclableArrayDeque newObject(Handle<RecyclableArrayDeque> handle) {
268                         return new RecyclableArrayDeque(DEFAULT_NUM_ELEMENTS, handle);
269                     }
270                 };
271 
272         public static RecyclableArrayDeque newInstance() {
273             return RECYCLER.get();
274         }
275 
276         private final Handle<RecyclableArrayDeque> handle;
277 
278         private RecyclableArrayDeque(int numElements, Handle<RecyclableArrayDeque> handle) {
279             super(numElements);
280             this.handle = handle;
281         }
282 
283         public void recycle() {
284             clear();
285             handle.recycle(this);
286         }
287     }
288 }