@@ -119,12 +119,26 @@ public partial class MultiplexingStream : IDisposableObservable, System.IAsyncDi
119119 /// </summary>
120120 private readonly int protocolMajorVersion ;
121121
122+ /// <summary>
123+ /// The largest receiving window any single channel may grow to via <see cref="ControlCode.ChannelWindowAdjust"/>.
124+ /// </summary>
125+ private readonly long maxChannelReceivingWindowSize ;
126+
122127 /// <summary>
123128 /// A value indicating whether any open channels should be faulted (i.e. their <see cref="Channel.Completion"/> task will be faulted)
124129 /// when the <see cref="MultiplexingStream"/> is disposed.
125130 /// </summary>
126131 private readonly bool faultOpenChannelsOnStreamDisposal ;
127132
133+ /// <summary>
134+ /// The number of bytes of receiving window growth (beyond each channel's initial window)
135+ /// that remain available to be committed across all channels of this stream.
136+ /// </summary>
137+ /// <remarks>
138+ /// Accessed only via <see cref="TryReserveWindowGrowth(long)"/> and <see cref="ReleaseWindowGrowth(long)"/>.
139+ /// </remarks>
140+ private long remainingWindowGrowthBudget ;
141+
128142 /// <summary>
129143 /// The last number assigned to a channel.
130144 /// Each use of this should increment by two, if <see cref="isOdd"/> has a value.
@@ -162,6 +176,8 @@ private MultiplexingStream(Formatter formatter, bool? isOdd, Options options)
162176
163177 this . DefaultChannelReceivingWindowSize = options . DefaultChannelReceivingWindowSize ;
164178 this . protocolMajorVersion = options . ProtocolMajorVersion ;
179+ this . maxChannelReceivingWindowSize = Math . Max ( options . MaxChannelReceivingWindowSize , options . DefaultChannelReceivingWindowSize ) ;
180+ this . remainingWindowGrowthBudget = options . MaxTotalChannelReceivingWindowSize ;
165181
166182 // Set up seed channels
167183 for ( int i = 0 ; i < options . SeededChannels . Count ; i ++ )
@@ -866,6 +882,12 @@ private async Task ReadStreamAsync()
866882 case ControlCode . ContentProcessed :
867883 this . OnContentProcessed ( header , frame . Value . Payload ) ;
868884 break ;
885+ case ControlCode . ChannelWindowAdjust :
886+ this . OnChannelWindowAdjust ( header , frame . Value . Payload ) ;
887+ break ;
888+ case ControlCode . ChannelWindowGrowthRequest :
889+ this . OnChannelWindowGrowthRequest ( header ) ;
890+ break ;
869891 case ControlCode . ContentWritingCompleted :
870892 this . OnContentWritingCompleted ( header . RequiredChannelId ) ;
871893 break ;
@@ -1086,6 +1108,72 @@ private void OnContentProcessed(FrameHeader header, ReadOnlySequence<byte> paylo
10861108 channel . OnContentProcessed ( bytesProcessed ) ;
10871109 }
10881110
1111+ private void OnChannelWindowAdjust ( FrameHeader header , ReadOnlySequence < byte > payloadBuffer )
1112+ {
1113+ Channel ? channel ;
1114+ lock ( this . syncObject )
1115+ {
1116+ this . openChannels . TryGetValue ( header . RequiredChannelId , out channel ) ;
1117+ }
1118+
1119+ if ( channel is null )
1120+ {
1121+ // The channel closed concurrently with the remote party's decision to enlarge its window.
1122+ // Dropping the adjustment is safe: there is nothing left to send.
1123+ return ;
1124+ }
1125+
1126+ long newWindowSize = this . formatter . DeserializeWindowSize ( payloadBuffer ) ;
1127+ channel . OnWindowAdjust ( newWindowSize ) ;
1128+ }
1129+
1130+ private void OnChannelWindowGrowthRequest ( FrameHeader header )
1131+ {
1132+ Channel ? channel ;
1133+ lock ( this . syncObject )
1134+ {
1135+ this . openChannels . TryGetValue ( header . RequiredChannelId , out channel ) ;
1136+ }
1137+
1138+ // A request for a channel that has since closed needs no answer.
1139+ channel ? . OnWindowGrowthRequested ( ) ;
1140+ }
1141+
1142+ /// <summary>
1143+ /// Attempts to claim a portion of this stream's total budget for receiving window growth.
1144+ /// </summary>
1145+ /// <param name="bytes">The number of additional bytes of window the caller wants to commit to.</param>
1146+ /// <returns><see langword="true"/> if the budget had room and has been debited; otherwise <see langword="false"/>.</returns>
1147+ private bool TryReserveWindowGrowth ( long bytes )
1148+ {
1149+ long remaining = Volatile . Read ( ref this . remainingWindowGrowthBudget ) ;
1150+ while ( remaining >= bytes )
1151+ {
1152+ long candidate = Interlocked . CompareExchange ( ref this . remainingWindowGrowthBudget , remaining - bytes , remaining ) ;
1153+ if ( candidate == remaining )
1154+ {
1155+ return true ;
1156+ }
1157+
1158+ remaining = candidate ;
1159+ }
1160+
1161+ return false ;
1162+ }
1163+
1164+ /// <summary>
1165+ /// Returns window growth budget previously claimed by <see cref="TryReserveWindowGrowth(long)"/>,
1166+ /// so that other channels may use it after a channel closes.
1167+ /// </summary>
1168+ /// <param name="bytes">The number of bytes to return to the budget.</param>
1169+ private void ReleaseWindowGrowth ( long bytes )
1170+ {
1171+ if ( bytes > 0 )
1172+ {
1173+ Interlocked . Add ( ref this . remainingWindowGrowthBudget , bytes ) ;
1174+ }
1175+ }
1176+
10891177 private void OnOffer ( QualifiedChannelId channelId , ReadOnlySequence < byte > payloadBuffer )
10901178 {
10911179 Channel . OfferParameters ? offerParameters = this . formatter . DeserializeOfferParameters ( payloadBuffer ) ;
0 commit comments