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