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 }