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 }