1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty.handler.codec.http2;
16
17 import io.netty.channel.ChannelHandlerContext;
18 import io.netty.util.internal.logging.InternalLogger;
19 import io.netty.util.internal.logging.InternalLoggerFactory;
20
21 import java.util.ArrayDeque;
22 import java.util.Deque;
23
24 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
25 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
26 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
27 import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
28 import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
29 import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
30 import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
31 import static io.netty.handler.codec.http2.Http2Exception.connectionError;
32 import static io.netty.handler.codec.http2.Http2Exception.streamError;
33 import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
34 import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
35 import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
36 import static io.netty.util.internal.ObjectUtil.checkNotNull;
37 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
38 import static java.lang.Math.max;
39 import static java.lang.Math.min;
40
41
42
43
44
45
46
47 public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowController {
48 private static final InternalLogger logger =
49 InternalLoggerFactory.getInstance(DefaultHttp2RemoteFlowController.class);
50 private static final int MIN_WRITABLE_CHUNK = 32 * 1024;
51 private final Http2Connection connection;
52 private final Http2Connection.PropertyKey stateKey;
53 private final StreamByteDistributor streamByteDistributor;
54 private final FlowState connectionState;
55 private int initialWindowSize = DEFAULT_WINDOW_SIZE;
56 private WritabilityMonitor monitor;
57 private ChannelHandlerContext ctx;
58
59 public DefaultHttp2RemoteFlowController(Http2Connection connection) {
60 this(connection, (Listener) null);
61 }
62
63 public DefaultHttp2RemoteFlowController(Http2Connection connection,
64 StreamByteDistributor streamByteDistributor) {
65 this(connection, streamByteDistributor, null);
66 }
67
68 public DefaultHttp2RemoteFlowController(Http2Connection connection, final Listener listener) {
69 this(connection, new WeightedFairQueueByteDistributor(connection), listener);
70 }
71
72 public DefaultHttp2RemoteFlowController(Http2Connection connection,
73 StreamByteDistributor streamByteDistributor,
74 final Listener listener) {
75 this.connection = checkNotNull(connection, "connection");
76 this.streamByteDistributor = checkNotNull(streamByteDistributor, "streamWriteDistributor");
77
78
79 stateKey = connection.newKey();
80 connectionState = new FlowState(connection.connectionStream());
81 connection.connectionStream().setProperty(stateKey, connectionState);
82
83
84 listener(listener);
85 monitor.windowSize(connectionState, initialWindowSize);
86
87
88 connection.addListener(new Http2ConnectionAdapter() {
89 @Override
90 public void onStreamAdded(Http2Stream stream) {
91
92
93 stream.setProperty(stateKey, new FlowState(stream));
94 }
95
96 @Override
97 public void onStreamActive(Http2Stream stream) {
98
99
100
101
102
103
104
105
106
107 FlowState state = state(stream);
108 long window = (long) initialWindowSize + state.windowSize();
109 if (window > MAX_INITIAL_WINDOW_SIZE) {
110 logger.warn("{} Window of stream {} would be {} when it becomes active, clamping it to {}",
111 ctx != null ? ctx.channel() : null, stream.id(), window, MAX_INITIAL_WINDOW_SIZE);
112 window = MAX_INITIAL_WINDOW_SIZE;
113 }
114 monitor.windowSize(state, (int) window);
115 }
116
117 @Override
118 public void onStreamClosed(Http2Stream stream) {
119
120
121 state(stream).cancel(STREAM_CLOSED, null);
122 }
123
124 @Override
125 public void onStreamHalfClosed(Http2Stream stream) {
126 if (HALF_CLOSED_LOCAL == stream.state()) {
127
128
129
130
131
132
133
134
135
136
137
138 state(stream).cancel(STREAM_CLOSED, null);
139 }
140 }
141 });
142 }
143
144
145
146
147
148
149 @Override
150 public void channelHandlerContext(ChannelHandlerContext ctx) throws Http2Exception {
151 this.ctx = checkNotNull(ctx, "ctx");
152
153
154
155 channelWritabilityChanged();
156
157
158
159
160
161 if (isChannelWritable()) {
162 writePendingBytes();
163 }
164 }
165
166 @Override
167 public ChannelHandlerContext channelHandlerContext() {
168 return ctx;
169 }
170
171 @Override
172 public void initialWindowSize(int newWindowSize) throws Http2Exception {
173 assert ctx == null || ctx.executor().inEventLoop();
174 monitor.initialWindowSize(newWindowSize);
175 }
176
177 @Override
178 public int initialWindowSize() {
179 return initialWindowSize;
180 }
181
182 @Override
183 public int windowSize(Http2Stream stream) {
184 return state(stream).windowSize();
185 }
186
187 @Override
188 public boolean isWritable(Http2Stream stream) {
189 return monitor.isWritable(state(stream));
190 }
191
192 @Override
193 public void channelWritabilityChanged() throws Http2Exception {
194 monitor.channelWritabilityChange();
195 }
196
197 @Override
198 public void updateDependencyTree(int childStreamId, int parentStreamId, short weight, boolean exclusive) {
199
200 assert weight >= MIN_WEIGHT && weight <= MAX_WEIGHT : "Invalid weight";
201 assert childStreamId != parentStreamId : "A stream cannot depend on itself";
202 assert childStreamId > 0 && parentStreamId >= 0 : "childStreamId must be > 0. parentStreamId must be >= 0.";
203
204 streamByteDistributor.updateDependencyTree(childStreamId, parentStreamId, weight, exclusive);
205 }
206
207 private boolean isChannelWritable() {
208 return ctx != null && isChannelWritable0();
209 }
210
211 private boolean isChannelWritable0() {
212 return ctx.channel().isWritable();
213 }
214
215 @Override
216 public void listener(Listener listener) {
217 monitor = listener == null ? new WritabilityMonitor() : new ListenerWritabilityMonitor(listener);
218 }
219
220 @Override
221 public void incrementWindowSize(Http2Stream stream, int delta) throws Http2Exception {
222 assert ctx == null || ctx.executor().inEventLoop();
223 monitor.incrementWindowSize(state(stream), delta);
224 }
225
226 @Override
227 public void addFlowControlled(Http2Stream stream, FlowControlled frame) {
228
229 assert ctx == null || ctx.executor().inEventLoop();
230 checkNotNull(frame, "frame");
231 try {
232 monitor.enqueueFrame(state(stream), frame);
233 } catch (Throwable t) {
234 frame.error(ctx, t);
235 }
236 }
237
238 @Override
239 public boolean hasFlowControlled(Http2Stream stream) {
240 return state(stream).hasFrame();
241 }
242
243 private FlowState state(Http2Stream stream) {
244 return (FlowState) stream.getProperty(stateKey);
245 }
246
247
248
249
250 private int connectionWindowSize() {
251 return connectionState.windowSize();
252 }
253
254 private int minUsableChannelBytes() {
255
256
257
258
259
260
261 return max(ctx.channel().config().getWriteBufferLowWaterMark(), MIN_WRITABLE_CHUNK);
262 }
263
264 private int maxUsableChannelBytes() {
265
266 int channelWritableBytes = (int) min(Integer.MAX_VALUE, ctx.channel().bytesBeforeUnwritable());
267 int usableBytes = channelWritableBytes > 0 ? max(channelWritableBytes, minUsableChannelBytes()) : 0;
268
269
270 return min(connectionState.windowSize(), usableBytes);
271 }
272
273
274
275
276
277 private int writableBytes() {
278 return min(connectionWindowSize(), maxUsableChannelBytes());
279 }
280
281 @Override
282 public void writePendingBytes() throws Http2Exception {
283 monitor.writePendingBytes();
284 }
285
286
287
288
289 private final class FlowState implements StreamByteDistributor.StreamState {
290 private final Http2Stream stream;
291 private final Deque<FlowControlled> pendingWriteQueue;
292 private int window;
293 private long pendingBytes;
294 private boolean markedWritable;
295
296
297
298
299 private boolean writing;
300
301
302
303 private boolean cancelled;
304
305 FlowState(Http2Stream stream) {
306 this.stream = stream;
307 pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
308 }
309
310
311
312
313 boolean isReserved() {
314 Http2Stream.State state = stream.state();
315 return state == RESERVED_LOCAL || state == RESERVED_REMOTE;
316 }
317
318
319
320
321
322 boolean isWritable() {
323 return windowSize() > pendingBytes() && !cancelled;
324 }
325
326
327
328
329 @Override
330 public Http2Stream stream() {
331 return stream;
332 }
333
334
335
336
337 boolean markedWritability() {
338 return markedWritable;
339 }
340
341
342
343
344 void markedWritability(boolean isWritable) {
345 this.markedWritable = isWritable;
346 }
347
348 @Override
349 public int windowSize() {
350 return window;
351 }
352
353
354
355
356 void windowSize(int initialWindowSize) {
357 window = initialWindowSize;
358 }
359
360
361
362
363
364 int writeAllocatedBytes(int allocated) {
365 final int initialAllocated = allocated;
366 int writtenBytes;
367
368 Throwable cause = null;
369 FlowControlled frame;
370 try {
371 assert !writing;
372 writing = true;
373
374
375 boolean writeOccurred = false;
376 while (!cancelled && (frame = peek()) != null) {
377 int maxBytes = min(allocated, writableWindow());
378 if (maxBytes <= 0 && frame.size() > 0) {
379
380
381 break;
382 }
383 writeOccurred = true;
384 int initialFrameSize = frame.size();
385 try {
386 frame.write(ctx, max(0, maxBytes));
387 if (frame.size() == 0) {
388
389
390
391 pendingWriteQueue.remove();
392 frame.writeComplete();
393 }
394 } finally {
395
396 allocated -= initialFrameSize - frame.size();
397 }
398 }
399
400 if (!writeOccurred) {
401
402 return -1;
403 }
404
405 } catch (Throwable t) {
406
407 cancelled = true;
408 cause = t;
409 } finally {
410 writing = false;
411
412
413 writtenBytes = initialAllocated - allocated;
414
415 decrementPendingBytes(writtenBytes, false);
416 decrementFlowControlWindow(writtenBytes);
417
418
419
420 if (cancelled) {
421 cancel(INTERNAL_ERROR, cause);
422 }
423 }
424 return writtenBytes;
425 }
426
427
428
429
430 int incrementStreamWindow(int delta) throws Http2Exception {
431 if (delta > 0 && Integer.MAX_VALUE - delta < window) {
432 throw streamError(stream.id(), FLOW_CONTROL_ERROR,
433 "Window size overflow for stream: %d", stream.id());
434 }
435 if (delta > 0 && isReserved() && (long) window + delta + initialWindowSize > MAX_INITIAL_WINDOW_SIZE) {
436
437
438 throw streamError(stream.id(), FLOW_CONTROL_ERROR,
439 "Window size overflow for reserved stream: %d", stream.id());
440 }
441 window += delta;
442
443 streamByteDistributor.updateStreamableBytes(this);
444 return window;
445 }
446
447
448
449
450 private int writableWindow() {
451 return min(window, connectionWindowSize());
452 }
453
454 @Override
455 public long pendingBytes() {
456 return pendingBytes;
457 }
458
459
460
461
462 void enqueueFrame(FlowControlled frame) {
463 FlowControlled last = pendingWriteQueue.peekLast();
464 if (last == null) {
465 enqueueFrameWithoutMerge(frame);
466 return;
467 }
468
469 int lastSize = last.size();
470 if (last.merge(ctx, frame)) {
471 incrementPendingBytes(last.size() - lastSize, true);
472 return;
473 }
474 enqueueFrameWithoutMerge(frame);
475 }
476
477 private void enqueueFrameWithoutMerge(FlowControlled frame) {
478 pendingWriteQueue.offer(frame);
479
480
481 incrementPendingBytes(frame.size(), true);
482 }
483
484 @Override
485 public boolean hasFrame() {
486 return !pendingWriteQueue.isEmpty();
487 }
488
489
490
491
492 private FlowControlled peek() {
493 return pendingWriteQueue.peek();
494 }
495
496
497
498
499
500
501 void cancel(Http2Error error, Throwable cause) {
502 cancelled = true;
503
504 if (writing) {
505 return;
506 }
507
508 FlowControlled frame = pendingWriteQueue.poll();
509 if (frame != null) {
510
511 final Http2Exception exception = streamError(stream.id(), error, cause,
512 "Stream closed before write could take place");
513 do {
514 writeError(frame, exception);
515 frame = pendingWriteQueue.poll();
516 } while (frame != null);
517 }
518
519 streamByteDistributor.updateStreamableBytes(this);
520
521 monitor.stateCancelled(this);
522 }
523
524
525
526
527
528 private void incrementPendingBytes(int numBytes, boolean updateStreamableBytes) {
529 pendingBytes += numBytes;
530 monitor.incrementPendingBytes(numBytes);
531 if (updateStreamableBytes) {
532 streamByteDistributor.updateStreamableBytes(this);
533 }
534 }
535
536
537
538
539 private void decrementPendingBytes(int bytes, boolean updateStreamableBytes) {
540 incrementPendingBytes(-bytes, updateStreamableBytes);
541 }
542
543
544
545
546 private void decrementFlowControlWindow(int bytes) {
547 try {
548 int negativeBytes = -bytes;
549 connectionState.incrementStreamWindow(negativeBytes);
550 incrementStreamWindow(negativeBytes);
551 } catch (Http2Exception e) {
552
553 throw new IllegalStateException("Invalid window state when writing frame: " + e.getMessage(), e);
554 }
555 }
556
557
558
559
560
561 private void writeError(FlowControlled frame, Http2Exception cause) {
562 assert ctx != null;
563 decrementPendingBytes(frame.size(), true);
564 frame.error(ctx, cause);
565 }
566 }
567
568
569
570
571 private class WritabilityMonitor implements StreamByteDistributor.Writer {
572 private boolean inWritePendingBytes;
573 private long totalPendingBytes;
574
575 @Override
576 public final void write(Http2Stream stream, int numBytes) {
577 state(stream).writeAllocatedBytes(numBytes);
578 }
579
580
581
582
583
584 void channelWritabilityChange() throws Http2Exception { }
585
586
587
588
589
590 void stateCancelled(FlowState state) { }
591
592
593
594
595
596
597 void windowSize(FlowState state, int initialWindowSize) {
598 state.windowSize(initialWindowSize);
599 }
600
601
602
603
604
605
606
607 void incrementWindowSize(FlowState state, int delta) throws Http2Exception {
608 state.incrementStreamWindow(delta);
609 }
610
611
612
613
614
615
616
617 void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception {
618 state.enqueueFrame(frame);
619 }
620
621
622
623
624
625
626 final void incrementPendingBytes(int delta) {
627 totalPendingBytes += delta;
628
629
630
631 }
632
633
634
635
636
637
638 final boolean isWritable(FlowState state) {
639 return isWritableConnection() && state.isWritable();
640 }
641
642 final void writePendingBytes() throws Http2Exception {
643
644
645
646
647
648 if (inWritePendingBytes) {
649 return;
650 }
651 inWritePendingBytes = true;
652 try {
653 int bytesToWrite = writableBytes();
654
655
656 for (;;) {
657 if (!streamByteDistributor.distribute(bytesToWrite, this) ||
658 (bytesToWrite = writableBytes()) <= 0 ||
659 !isChannelWritable0()) {
660 break;
661 }
662 }
663 } finally {
664 inWritePendingBytes = false;
665 }
666 }
667
668 void initialWindowSize(int newWindowSize) throws Http2Exception {
669 checkPositiveOrZero(newWindowSize, "newWindowSize");
670
671 final int delta = newWindowSize - initialWindowSize;
672 initialWindowSize = newWindowSize;
673 connection.forEachActiveStream(new Http2StreamVisitor() {
674 @Override
675 public boolean visit(Http2Stream stream) throws Http2Exception {
676 try {
677 state(stream).incrementStreamWindow(delta);
678 } catch (Http2Exception e) {
679
680 throw connectionError(FLOW_CONTROL_ERROR, e,
681 "Window size overflow for stream: %d after SETTINGS_INITIAL_WINDOW_SIZE change",
682 stream.id());
683 }
684 return true;
685 }
686 });
687
688 if (delta > 0 && isChannelWritable()) {
689
690 writePendingBytes();
691 }
692 }
693
694 final boolean isWritableConnection() {
695 return connectionState.windowSize() - totalPendingBytes > 0 && isChannelWritable();
696 }
697 }
698
699
700
701
702
703
704
705
706
707 private final class ListenerWritabilityMonitor extends WritabilityMonitor implements Http2StreamVisitor {
708 private final Listener listener;
709
710 ListenerWritabilityMonitor(Listener listener) {
711 this.listener = listener;
712 }
713
714 @Override
715 public boolean visit(Http2Stream stream) throws Http2Exception {
716 FlowState state = state(stream);
717 if (isWritable(state) != state.markedWritability()) {
718 notifyWritabilityChanged(state);
719 }
720 return true;
721 }
722
723 @Override
724 void windowSize(FlowState state, int initialWindowSize) {
725 super.windowSize(state, initialWindowSize);
726 try {
727 checkStateWritability(state);
728 } catch (Http2Exception e) {
729 throw new RuntimeException("Caught unexpected exception from window", e);
730 }
731 }
732
733 @Override
734 void incrementWindowSize(FlowState state, int delta) throws Http2Exception {
735 super.incrementWindowSize(state, delta);
736 checkStateWritability(state);
737 }
738
739 @Override
740 void initialWindowSize(int newWindowSize) throws Http2Exception {
741 super.initialWindowSize(newWindowSize);
742 if (isWritableConnection()) {
743
744
745 checkAllWritabilityChanged();
746 }
747 }
748
749 @Override
750 void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception {
751 super.enqueueFrame(state, frame);
752 checkConnectionThenStreamWritabilityChanged(state);
753 }
754
755 @Override
756 void stateCancelled(FlowState state) {
757 try {
758 checkConnectionThenStreamWritabilityChanged(state);
759 } catch (Http2Exception e) {
760 throw new RuntimeException("Caught unexpected exception from checkAllWritabilityChanged", e);
761 }
762 }
763
764 @Override
765 void channelWritabilityChange() throws Http2Exception {
766 if (connectionState.markedWritability() != isChannelWritable()) {
767 checkAllWritabilityChanged();
768 }
769 }
770
771 private void checkStateWritability(FlowState state) throws Http2Exception {
772 if (isWritable(state) != state.markedWritability()) {
773 if (state == connectionState) {
774 checkAllWritabilityChanged();
775 } else {
776 notifyWritabilityChanged(state);
777 }
778 }
779 }
780
781 private void notifyWritabilityChanged(FlowState state) {
782 state.markedWritability(!state.markedWritability());
783 try {
784 listener.writabilityChanged(state.stream);
785 } catch (Throwable cause) {
786 logger.error("{} Caught Throwable from listener.writabilityChanged for {}",
787 ctx.channel(), state.stream, cause);
788 }
789 }
790
791 private void checkConnectionThenStreamWritabilityChanged(FlowState state) throws Http2Exception {
792
793 if (isWritableConnection() != connectionState.markedWritability()) {
794 checkAllWritabilityChanged();
795 } else if (isWritable(state) != state.markedWritability()) {
796 notifyWritabilityChanged(state);
797 }
798 }
799
800 private void checkAllWritabilityChanged() throws Http2Exception {
801
802 connectionState.markedWritability(isWritableConnection());
803 connection.forEachActiveStream(this);
804 }
805 }
806 }