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