View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * 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 distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
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   * Basic implementation of {@link Http2RemoteFlowController}.
43   * <p>
44   * This class is <strong>NOT</strong> thread safe. The assumption is all methods must be invoked from a single thread.
45   * Typically this thread is the event loop thread for the {@link ChannelHandlerContext} managed by this class.
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          // Add a flow state for the connection.
79          stateKey = connection.newKey();
80          connectionState = new FlowState(connection.connectionStream());
81          connection.connectionStream().setProperty(stateKey, connectionState);
82  
83          // Monitor may depend upon connectionState, and so initialize after connectionState
84          listener(listener);
85          monitor.windowSize(connectionState, initialWindowSize);
86  
87          // Register for notification of new streams.
88          connection.addListener(new Http2ConnectionAdapter() {
89              @Override
90              public void onStreamAdded(Http2Stream stream) {
91                  // If the stream state is not open then the stream is not yet eligible for flow controlled frames and
92                  // only requires the ReducedFlowState. Otherwise the full amount of memory is required.
93                  stream.setProperty(stateKey, new FlowState(stream));
94              }
95  
96              @Override
97              public void onStreamActive(Http2Stream stream) {
98                  // If the object was previously created, but later activated then we have to ensure the proper
99                  // initialWindowSize is used. The peer might also have already granted credit for a reserved stream
100                 // via WINDOW_UPDATE (RFC 9113, section 5.1), so add the initialWindowSize to it instead of replacing
101                 // it. The window of a stream that was not reserved before is always 0 here. A WINDOW_UPDATE that would
102                 // make the sum exceed MAX_INITIAL_WINDOW_SIZE is rejected (see FlowState.incrementStreamWindow), but a
103                 // SETTINGS_INITIAL_WINDOW_SIZE increase after such credit was granted can still do so. Unlike for
104                 // active streams this is not detected when the SETTINGS are applied, as only active streams are
105                 // visited there, and we can't signal an error from here. It needs a misbehaving peer, so clamp the
106                 // window and log it.
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                 // Any pending frames can never be written, cancel and
120                 // write errors for any pending frames.
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                      * When this method is called there should not be any
129                      * pending frames left if the API is used correctly. However,
130                      * it is possible that a erroneous application can sneak
131                      * in a frame even after having already written a frame with the
132                      * END_STREAM flag set, as the stream state might not transition
133                      * immediately to HALF_CLOSED_LOCAL / CLOSED due to flow control
134                      * delaying the write.
135                      *
136                      * This is to cancel any such illegal writes.
137                      */
138                     state(stream).cancel(STREAM_CLOSED, null);
139                 }
140             }
141         });
142     }
143 
144     /**
145      * {@inheritDoc}
146      * <p>
147      * Any queued {@link FlowControlled} objects will be sent.
148      */
149     @Override
150     public void channelHandlerContext(ChannelHandlerContext ctx) throws Http2Exception {
151         this.ctx = checkNotNull(ctx, "ctx");
152 
153         // Writing the pending bytes will not check writability change and instead a writability change notification
154         // to be provided by an explicit call.
155         channelWritabilityChanged();
156 
157         // Don't worry about cleaning up queued frames here if ctx is null. It is expected that all streams will be
158         // closed and the queue cleanup will occur when the stream state transitions occur.
159 
160         // If any frames have been queued up, we should send them now that we have a channel context.
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         // It is assumed there are all validated at a higher level. For example in the Http2FrameReader.
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         // The context can be null assuming the frame will be queued and send later when the context is set.
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      * Returns the flow control window for the entire connection.
249      */
250     private int connectionWindowSize() {
251         return connectionState.windowSize();
252     }
253 
254     private int minUsableChannelBytes() {
255         // The current allocation algorithm values "fairness" and doesn't give any consideration to "goodput". It
256         // is possible that 1 byte will be allocated to many streams. In an effort to try to make "goodput"
257         // reasonable with the current allocation algorithm we have this "cheap" check up front to ensure there is
258         // an "adequate" amount of connection window before allocation is attempted. This is not foolproof as if the
259         // number of streams is >= this minimal number then we may still have the issue, but the idea is to narrow the
260         // circumstances in which this can happen without rewriting the allocation algorithm.
261         return max(ctx.channel().config().getWriteBufferLowWaterMark(), MIN_WRITABLE_CHUNK);
262     }
263 
264     private int maxUsableChannelBytes() {
265         // If the channel isWritable, allow at least minUsableChannelBytes.
266         int channelWritableBytes = (int) min(Integer.MAX_VALUE, ctx.channel().bytesBeforeUnwritable());
267         int usableBytes = channelWritableBytes > 0 ? max(channelWritableBytes, minUsableChannelBytes()) : 0;
268 
269         // Clip the usable bytes by the connection window.
270         return min(connectionState.windowSize(), usableBytes);
271     }
272 
273     /**
274      * The amount of bytes that can be supported by underlying {@link io.netty.channel.Channel} without
275      * queuing "too-much".
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      * The remote flow control state for a single stream.
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          * Set to true while a frame is being written, false otherwise.
298          */
299         private boolean writing;
300         /**
301          * Set to true if cancel() was called.
302          */
303         private boolean cancelled;
304 
305         FlowState(Http2Stream stream) {
306             this.stream = stream;
307             pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
308         }
309 
310         /**
311          * Determine if the stream associated with this object is reserved, so it is not active yet.
312          */
313         boolean isReserved() {
314             Http2Stream.State state = stream.state();
315             return state == RESERVED_LOCAL || state == RESERVED_REMOTE;
316         }
317 
318         /**
319          * Determine if the stream associated with this object is writable.
320          * @return {@code true} if the stream associated with this object is writable.
321          */
322         boolean isWritable() {
323             return windowSize() > pendingBytes() && !cancelled;
324         }
325 
326         /**
327          * The stream this state is associated with.
328          */
329         @Override
330         public Http2Stream stream() {
331             return stream;
332         }
333 
334         /**
335          * Returns the parameter from the last call to {@link #markedWritability(boolean)}.
336          */
337         boolean markedWritability() {
338             return markedWritable;
339         }
340 
341         /**
342          * Save the state of writability.
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          * Reset the window size for this stream.
355          */
356         void windowSize(int initialWindowSize) {
357             window = initialWindowSize;
358         }
359 
360         /**
361          * Write the allocated bytes for this stream.
362          * @return the number of bytes written for a stream or {@code -1} if no write occurred.
363          */
364         int writeAllocatedBytes(int allocated) {
365             final int initialAllocated = allocated;
366             int writtenBytes;
367             // In case an exception is thrown we want to remember it and pass it to cancel(Throwable).
368             Throwable cause = null;
369             FlowControlled frame;
370             try {
371                 assert !writing;
372                 writing = true;
373 
374                 // Write the remainder of frames that we are allowed to
375                 boolean writeOccurred = false;
376                 while (!cancelled && (frame = peek()) != null) {
377                     int maxBytes = min(allocated, writableWindow());
378                     if (maxBytes <= 0 && frame.size() > 0) {
379                         // The frame still has data, but the amount of allocated bytes has been exhausted.
380                         // Don't write needless empty frames.
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                             // This frame has been fully written, remove this frame and notify it.
389                             // Since we remove this frame first, we're guaranteed that its error
390                             // method will not be called when we call cancel.
391                             pendingWriteQueue.remove();
392                             frame.writeComplete();
393                         }
394                     } finally {
395                         // Decrement allocated by how much was actually written.
396                         allocated -= initialFrameSize - frame.size();
397                     }
398                 }
399 
400                 if (!writeOccurred) {
401                     // Either there was no frame, or the amount of allocated bytes has been exhausted.
402                     return -1;
403                 }
404 
405             } catch (Throwable t) {
406                 // Mark the state as cancelled, we'll clear the pending queue via cancel() below.
407                 cancelled = true;
408                 cause = t;
409             } finally {
410                 writing = false;
411                 // Make sure we always decrement the flow control windows
412                 // by the bytes written.
413                 writtenBytes = initialAllocated - allocated;
414 
415                 decrementPendingBytes(writtenBytes, false);
416                 decrementFlowControlWindow(writtenBytes);
417 
418                 // If a cancellation occurred while writing, call cancel again to
419                 // clear and error all of the pending writes.
420                 if (cancelled) {
421                     cancel(INTERNAL_ERROR, cause);
422                 }
423             }
424             return writtenBytes;
425         }
426 
427         /**
428          * Increments the flow control window for this stream by the given delta and returns the new value.
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                 // The initialWindowSize is added to the window of a reserved stream when it becomes active, and the
437                 // window must not exceed 2^31-1 then either (RFC 9113, section 6.9.1).
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          * Returns the maximum writable window (minimum of the stream and connection windows).
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          * Adds the {@code frame} to the pending queue and increments the pending byte count.
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             // This must be called after adding to the queue in order so that hasFrame() is
480             // updated before updating the stream state.
481             incrementPendingBytes(frame.size(), true);
482         }
483 
484         @Override
485         public boolean hasFrame() {
486             return !pendingWriteQueue.isEmpty();
487         }
488 
489         /**
490          * Returns the head of the pending queue, or {@code null} if empty.
491          */
492         private FlowControlled peek() {
493             return pendingWriteQueue.peek();
494         }
495 
496         /**
497          * Clears the pending queue and writes errors for each remaining frame.
498          * @param error the {@link Http2Error} to use.
499          * @param cause the {@link Throwable} that caused this method to be invoked.
500          */
501         void cancel(Http2Error error, Throwable cause) {
502             cancelled = true;
503             // Ensure that the queue can't be modified while we are writing.
504             if (writing) {
505                 return;
506             }
507 
508             FlowControlled frame = pendingWriteQueue.poll();
509             if (frame != null) {
510                 // Only create exception once and reuse to reduce overhead of filling in the stacktrace.
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          * Increments the number of pending bytes for this node and optionally updates the
526          * {@link StreamByteDistributor}.
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          * If this frame is in the pending queue, decrements the number of pending bytes for the stream.
538          */
539         private void decrementPendingBytes(int bytes, boolean updateStreamableBytes) {
540             incrementPendingBytes(-bytes, updateStreamableBytes);
541         }
542 
543         /**
544          * Decrement the per stream and connection flow control window by {@code bytes}.
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                 // Should never get here since we're decrementing.
553                 throw new IllegalStateException("Invalid window state when writing frame: " + e.getMessage(), e);
554             }
555         }
556 
557         /**
558          * Discards this {@link FlowControlled}, writing an error. If this frame is in the pending queue,
559          * the unwritten bytes are removed from this branch of the priority tree.
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      * Abstract class which provides common functionality for writability monitor implementations.
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          * Called when the writability of the underlying channel changes.
582          * @throws Http2Exception If a write occurs and an exception happens in the write operation.
583          */
584         void channelWritabilityChange() throws Http2Exception { }
585 
586         /**
587          * Called when the state is cancelled.
588          * @param state the state that was cancelled.
589          */
590         void stateCancelled(FlowState state) { }
591 
592         /**
593          * Set the initial window size for {@code state}.
594          * @param state the state to change the initial window size for.
595          * @param initialWindowSize the size of the window in bytes.
596          */
597         void windowSize(FlowState state, int initialWindowSize) {
598             state.windowSize(initialWindowSize);
599         }
600 
601         /**
602          * Increment the window size for a particular stream.
603          * @param state the state associated with the stream whose window is being incremented.
604          * @param delta The amount to increment by.
605          * @throws Http2Exception If this operation overflows the window for {@code state}.
606          */
607         void incrementWindowSize(FlowState state, int delta) throws Http2Exception {
608             state.incrementStreamWindow(delta);
609         }
610 
611         /**
612          * Add a frame to be sent via flow control.
613          * @param state The state associated with the stream which the {@code frame} is associated with.
614          * @param frame the frame to enqueue.
615          * @throws Http2Exception If a writability error occurs.
616          */
617         void enqueueFrame(FlowState state, FlowControlled frame) throws Http2Exception {
618             state.enqueueFrame(frame);
619         }
620 
621         /**
622          * Increment the total amount of pending bytes for all streams. When any stream's pending bytes changes
623          * method should be called.
624          * @param delta The amount to increment by.
625          */
626         final void incrementPendingBytes(int delta) {
627             totalPendingBytes += delta;
628 
629             // Notification of writibilty change should be delayed until the end of the top level event.
630             // This is to ensure the flow controller is more consistent state before calling external listener methods.
631         }
632 
633         /**
634          * Determine if the stream associated with {@code state} is writable.
635          * @param state The state which is associated with the stream to test writability for.
636          * @return {@code true} if {@link FlowState#stream()} is writable. {@code false} otherwise.
637          */
638         final boolean isWritable(FlowState state) {
639             return isWritableConnection() && state.isWritable();
640         }
641 
642         final void writePendingBytes() throws Http2Exception {
643             // Reentry is not permitted during the byte distribution process. It may lead to undesirable distribution of
644             // bytes and even infinite loops. We protect against reentry and make sure each call has an opportunity to
645             // cause a distribution to occur. This may be useful for example if the channel's writability changes from
646             // Writable -> Not Writable (because we are writing) -> Writable (because the user flushed to make more room
647             // in the channel outbound buffer).
648             if (inWritePendingBytes) {
649                 return;
650             }
651             inWritePendingBytes = true;
652             try {
653                 int bytesToWrite = writableBytes();
654                 // Make sure we always write at least once, regardless if we have bytesToWrite or not.
655                 // This ensures that zero-length frames will always be written.
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                         // RFC 9113 Section 6.9.2 requires a connection error if a SETTINGS change causes overflow.
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                 // The window size increased, send any pending frames for all streams.
690                 writePendingBytes();
691             }
692         }
693 
694         final boolean isWritableConnection() {
695             return connectionState.windowSize() - totalPendingBytes > 0 && isChannelWritable();
696         }
697     }
698 
699     /**
700      * Writability of a {@code stream} is calculated using the following:
701      * <pre>
702      * Connection Window - Total Queued Bytes > 0 &&
703      * Stream Window - Bytes Queued for Stream > 0 &&
704      * isChannelWritable()
705      * </pre>
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                 // If the write operation does not occur we still need to check all streams because they
744                 // may have transitioned from writable to not writable.
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             // It is possible that the connection window and/or the individual stream writability could change.
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             // Make sure we mark that we have notified as a result of this change.
802             connectionState.markedWritability(isWritableConnection());
803             connection.forEachActiveStream(this);
804         }
805     }
806 }