1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http2;
17
18 import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
19 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
20 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
21 import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_INITIAL_WINDOW_SIZE;
22 import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
23 import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
24 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
25 import static io.netty.handler.codec.http2.Http2Exception.streamError;
26 import static io.netty.util.internal.ObjectUtil.checkNotNull;
27 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
28 import static java.lang.Math.max;
29 import static java.lang.Math.min;
30 import io.netty.buffer.ByteBuf;
31 import io.netty.channel.ChannelHandlerContext;
32 import io.netty.handler.codec.http2.Http2Exception.CompositeStreamException;
33 import io.netty.handler.codec.http2.Http2Exception.StreamException;
34 import io.netty.util.internal.PlatformDependent;
35
36
37
38
39
40
41
42 public class DefaultHttp2LocalFlowController implements Http2LocalFlowController {
43
44
45
46
47 public static final float DEFAULT_WINDOW_UPDATE_RATIO = 0.5f;
48
49 private final Http2Connection connection;
50 private final Http2Connection.PropertyKey stateKey;
51 private Http2FrameWriter frameWriter;
52 private ChannelHandlerContext ctx;
53 private float windowUpdateRatio;
54 private int initialWindowSize = DEFAULT_WINDOW_SIZE;
55
56 public DefaultHttp2LocalFlowController(Http2Connection connection) {
57 this(connection, DEFAULT_WINDOW_UPDATE_RATIO, false);
58 }
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public DefaultHttp2LocalFlowController(Http2Connection connection,
74 float windowUpdateRatio,
75 boolean autoRefillConnectionWindow) {
76 this.connection = checkNotNull(connection, "connection");
77 windowUpdateRatio(windowUpdateRatio);
78
79
80 stateKey = connection.newKey();
81 FlowState connectionState = autoRefillConnectionWindow ?
82 new AutoRefillState(connection.connectionStream(), initialWindowSize) :
83 new DefaultState(connection.connectionStream(), initialWindowSize);
84 connection.connectionStream().setProperty(stateKey, connectionState);
85
86
87 connection.addListener(new Http2ConnectionAdapter() {
88 @Override
89 public void onStreamAdded(Http2Stream stream) {
90
91
92 stream.setProperty(stateKey, REDUCED_FLOW_STATE);
93 }
94
95 @Override
96 public void onStreamActive(Http2Stream stream) {
97
98
99 stream.setProperty(stateKey, new DefaultState(stream, initialWindowSize));
100 }
101
102 @Override
103 public void onStreamClosed(Http2Stream stream) {
104 try {
105
106
107 FlowState state = state(stream);
108 int unconsumedBytes = state.unconsumedBytes();
109 if (ctx != null && unconsumedBytes > 0) {
110 if (consumeAllBytes(state, unconsumedBytes)) {
111
112
113 ctx.flush();
114 }
115 }
116 } catch (Http2Exception e) {
117 PlatformDependent.throwException(e);
118 } finally {
119
120
121
122 stream.setProperty(stateKey, REDUCED_FLOW_STATE);
123 }
124 }
125 });
126 }
127
128 @Override
129 public DefaultHttp2LocalFlowController frameWriter(Http2FrameWriter frameWriter) {
130 this.frameWriter = checkNotNull(frameWriter, "frameWriter");
131 return this;
132 }
133
134 @Override
135 public void channelHandlerContext(ChannelHandlerContext ctx) {
136 this.ctx = checkNotNull(ctx, "ctx");
137 }
138
139 @Override
140 public void initialWindowSize(int newWindowSize) throws Http2Exception {
141 assert ctx == null || ctx.executor().inEventLoop();
142 int delta = newWindowSize - initialWindowSize;
143 initialWindowSize = newWindowSize;
144
145 WindowUpdateVisitor visitor = new WindowUpdateVisitor(delta);
146 connection.forEachActiveStream(visitor);
147 visitor.throwIfError();
148 }
149
150 @Override
151 public int initialWindowSize() {
152 return initialWindowSize;
153 }
154
155 @Override
156 public int windowSize(Http2Stream stream) {
157 return state(stream).windowSize();
158 }
159
160 @Override
161 public int initialWindowSize(Http2Stream stream) {
162 return state(stream).initialWindowSize();
163 }
164
165 @Override
166 public void incrementWindowSize(Http2Stream stream, int delta) throws Http2Exception {
167 assert ctx != null && ctx.executor().inEventLoop();
168 FlowState state = state(stream);
169
170
171 state.incrementInitialStreamWindow(delta);
172 state.writeWindowUpdateIfNeeded();
173 }
174
175 @Override
176 public boolean consumeBytes(Http2Stream stream, int numBytes) throws Http2Exception {
177 assert ctx != null && ctx.executor().inEventLoop();
178 checkPositiveOrZero(numBytes, "numBytes");
179 if (numBytes == 0) {
180 return false;
181 }
182
183 if (stream != null) {
184 if (stream.id() == CONNECTION_STREAM_ID) {
185 throw new UnsupportedOperationException("Returning bytes for the connection window is not supported");
186 }
187
188
189
190 if (isActive(stream)) {
191 return consumeAllBytes(state(stream), numBytes);
192 }
193 }
194 return false;
195 }
196
197 private boolean consumeAllBytes(FlowState state, int numBytes) throws Http2Exception {
198 return connectionState().consumeBytes(numBytes) | state.consumeBytes(numBytes);
199 }
200
201 @Override
202 public int unconsumedBytes(Http2Stream stream) {
203 return state(stream).unconsumedBytes();
204 }
205
206 private static void checkValidRatio(float ratio) {
207 if (Double.compare(ratio, 0.0) <= 0 || Double.compare(ratio, 1.0) >= 0) {
208 throw new IllegalArgumentException("Invalid ratio: " + ratio);
209 }
210 }
211
212
213
214
215
216
217
218
219 public void windowUpdateRatio(float ratio) {
220 assert ctx == null || ctx.executor().inEventLoop();
221 checkValidRatio(ratio);
222 windowUpdateRatio = ratio;
223 }
224
225
226
227
228
229
230 public float windowUpdateRatio() {
231 return windowUpdateRatio;
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 public void windowUpdateRatio(Http2Stream stream, float ratio) throws Http2Exception {
248 assert ctx != null && ctx.executor().inEventLoop();
249 checkValidRatio(ratio);
250 FlowState state = state(stream);
251 state.windowUpdateRatio(ratio);
252 state.writeWindowUpdateIfNeeded();
253 }
254
255
256
257
258
259
260
261 public float windowUpdateRatio(Http2Stream stream) throws Http2Exception {
262 return state(stream).windowUpdateRatio();
263 }
264
265 @Override
266 public void receiveFlowControlledFrame(Http2Stream stream, ByteBuf data, int padding,
267 boolean endOfStream) throws Http2Exception {
268 assert ctx != null && ctx.executor().inEventLoop();
269 int dataLength = data.readableBytes() + padding;
270
271
272 FlowState connectionState = connectionState();
273 connectionState.receiveFlowControlledFrame(dataLength);
274
275 if (stream != null && isActive(stream)) {
276
277 FlowState state = state(stream);
278 state.endOfStream(endOfStream);
279 state.receiveFlowControlledFrame(dataLength);
280 } else if (dataLength > 0) {
281
282 connectionState.consumeBytes(dataLength);
283 }
284 }
285
286 private FlowState connectionState() {
287 return connection.connectionStream().getProperty(stateKey);
288 }
289
290 private FlowState state(Http2Stream stream) {
291 return stream.getProperty(stateKey);
292 }
293
294
295
296
297
298 private static boolean isActive(Http2Stream stream) {
299 switch (stream.state()) {
300 case OPEN:
301 case HALF_CLOSED_LOCAL:
302 case HALF_CLOSED_REMOTE:
303 return true;
304 default:
305 return false;
306 }
307 }
308
309 private static boolean isClosed(Http2Stream stream) {
310 return stream.state() == Http2Stream.State.CLOSED;
311 }
312
313
314
315
316
317 private final class AutoRefillState extends DefaultState {
318 AutoRefillState(Http2Stream stream, int initialWindowSize) {
319 super(stream, initialWindowSize);
320 }
321
322 @Override
323 public void receiveFlowControlledFrame(int dataLength) throws Http2Exception {
324 super.receiveFlowControlledFrame(dataLength);
325
326 super.consumeBytes(dataLength);
327 }
328
329 @Override
330 public boolean consumeBytes(int numBytes) throws Http2Exception {
331
332 return false;
333 }
334 }
335
336
337
338
339 private class DefaultState implements FlowState {
340 private final Http2Stream stream;
341
342
343
344
345 private int window;
346
347
348
349
350
351
352
353 private int processedWindow;
354
355
356
357
358
359 private int initialStreamWindowSize;
360
361
362
363
364
365
366 private float streamWindowUpdateRatio;
367
368 private int lowerBound;
369 private boolean endOfStream;
370
371 DefaultState(Http2Stream stream, int initialWindowSize) {
372 this.stream = stream;
373 window(initialWindowSize);
374 streamWindowUpdateRatio = windowUpdateRatio;
375 }
376
377 @Override
378 public void window(int initialWindowSize) {
379 assert ctx == null || ctx.executor().inEventLoop();
380 window = processedWindow = initialStreamWindowSize = initialWindowSize;
381 }
382
383 @Override
384 public int windowSize() {
385 return window;
386 }
387
388 @Override
389 public int initialWindowSize() {
390 return initialStreamWindowSize;
391 }
392
393 @Override
394 public void endOfStream(boolean endOfStream) {
395 this.endOfStream = endOfStream;
396 }
397
398 @Override
399 public float windowUpdateRatio() {
400 return streamWindowUpdateRatio;
401 }
402
403 @Override
404 public void windowUpdateRatio(float ratio) {
405 assert ctx == null || ctx.executor().inEventLoop();
406 streamWindowUpdateRatio = ratio;
407 }
408
409 @Override
410 public void incrementInitialStreamWindow(int delta) {
411
412 int newValue = (int) min(MAX_INITIAL_WINDOW_SIZE,
413 max(MIN_INITIAL_WINDOW_SIZE, initialStreamWindowSize + (long) delta));
414 delta = newValue - initialStreamWindowSize;
415
416 initialStreamWindowSize += delta;
417 }
418
419 @Override
420 public void incrementFlowControlWindows(int delta) throws Http2Exception {
421 if (delta > 0 && window > MAX_INITIAL_WINDOW_SIZE - delta) {
422 throw streamError(stream.id(), FLOW_CONTROL_ERROR,
423 "Flow control window overflowed for stream: %d", stream.id());
424 }
425
426 window += delta;
427 processedWindow += delta;
428 lowerBound = min(delta, 0);
429 }
430
431 @Override
432 public void receiveFlowControlledFrame(int dataLength) throws Http2Exception {
433 assert dataLength >= 0;
434
435
436 window -= dataLength;
437
438
439
440
441
442
443 if (window < lowerBound) {
444 throw streamError(stream.id(), FLOW_CONTROL_ERROR,
445 "Flow control window exceeded for stream: %d", stream.id());
446 }
447 }
448
449 private void returnProcessedBytes(int delta) throws Http2Exception {
450 if (processedWindow - delta < window) {
451 throw streamError(stream.id(), INTERNAL_ERROR,
452 "Attempting to return too many bytes for stream %d", stream.id());
453 }
454 processedWindow -= delta;
455 }
456
457 @Override
458 public boolean consumeBytes(int numBytes) throws Http2Exception {
459
460 returnProcessedBytes(numBytes);
461 return writeWindowUpdateIfNeeded();
462 }
463
464 @Override
465 public int unconsumedBytes() {
466 return processedWindow - window;
467 }
468
469 @Override
470 public boolean writeWindowUpdateIfNeeded() throws Http2Exception {
471 if (endOfStream || initialStreamWindowSize <= 0 ||
472
473 isClosed(stream)) {
474 return false;
475 }
476
477 int threshold = (int) (initialStreamWindowSize * streamWindowUpdateRatio);
478 if (processedWindow <= threshold) {
479 writeWindowUpdate();
480 return true;
481 }
482 return false;
483 }
484
485
486
487
488
489 private void writeWindowUpdate() throws Http2Exception {
490
491 int deltaWindowSize = initialStreamWindowSize - processedWindow;
492 try {
493 incrementFlowControlWindows(deltaWindowSize);
494 } catch (Throwable t) {
495 throw connectionError(INTERNAL_ERROR, t,
496 "Attempting to return too many bytes for stream %d", stream.id());
497 }
498
499
500 frameWriter.writeWindowUpdate(ctx, stream.id(), deltaWindowSize, ctx.newPromise());
501 }
502 }
503
504
505
506
507
508 private static final FlowState REDUCED_FLOW_STATE = new FlowState() {
509
510 @Override
511 public int windowSize() {
512 return 0;
513 }
514
515 @Override
516 public int initialWindowSize() {
517 return 0;
518 }
519
520 @Override
521 public void window(int initialWindowSize) {
522 throw new UnsupportedOperationException();
523 }
524
525 @Override
526 public void incrementInitialStreamWindow(int delta) {
527
528
529 }
530
531 @Override
532 public boolean writeWindowUpdateIfNeeded() throws Http2Exception {
533 throw new UnsupportedOperationException();
534 }
535
536 @Override
537 public boolean consumeBytes(int numBytes) throws Http2Exception {
538 return false;
539 }
540
541 @Override
542 public int unconsumedBytes() {
543 return 0;
544 }
545
546 @Override
547 public float windowUpdateRatio() {
548 throw new UnsupportedOperationException();
549 }
550
551 @Override
552 public void windowUpdateRatio(float ratio) {
553 throw new UnsupportedOperationException();
554 }
555
556 @Override
557 public void receiveFlowControlledFrame(int dataLength) throws Http2Exception {
558 throw new UnsupportedOperationException();
559 }
560
561 @Override
562 public void incrementFlowControlWindows(int delta) throws Http2Exception {
563
564
565 }
566
567 @Override
568 public void endOfStream(boolean endOfStream) {
569 throw new UnsupportedOperationException();
570 }
571 };
572
573
574
575
576 private interface FlowState {
577
578 int windowSize();
579
580 int initialWindowSize();
581
582 void window(int initialWindowSize);
583
584
585
586
587
588 void incrementInitialStreamWindow(int delta);
589
590
591
592
593
594
595 boolean writeWindowUpdateIfNeeded() throws Http2Exception;
596
597
598
599
600
601
602
603
604
605 boolean consumeBytes(int numBytes) throws Http2Exception;
606
607 int unconsumedBytes();
608
609 float windowUpdateRatio();
610
611 void windowUpdateRatio(float ratio);
612
613
614
615
616
617
618 void receiveFlowControlledFrame(int dataLength) throws Http2Exception;
619
620
621
622
623
624
625 void incrementFlowControlWindows(int delta) throws Http2Exception;
626
627 void endOfStream(boolean endOfStream);
628 }
629
630
631
632
633 private final class WindowUpdateVisitor implements Http2StreamVisitor {
634 private CompositeStreamException compositeException;
635 private final int delta;
636
637 WindowUpdateVisitor(int delta) {
638 this.delta = delta;
639 }
640
641 @Override
642 public boolean visit(Http2Stream stream) throws Http2Exception {
643 try {
644
645 FlowState state = state(stream);
646 state.incrementFlowControlWindows(delta);
647 state.incrementInitialStreamWindow(delta);
648 } catch (StreamException e) {
649 if (compositeException == null) {
650 compositeException = new CompositeStreamException(e.error(), 4);
651 }
652 compositeException.add(e);
653 }
654 return true;
655 }
656
657 public void throwIfError() throws CompositeStreamException {
658 if (compositeException != null) {
659 throw compositeException;
660 }
661 }
662 }
663 }