@@ -49,11 +49,6 @@ internal sealed class Http3Connection : HttpConnectionBase, IDisposable
4949 private int _haveServerQpackDecodeStream ;
5050 private int _haveServerQpackEncodeStream ;
5151
52- // Manages MAX_STREAM count from server.
53- private long _maximumRequestStreams ;
54- private long _requestStreamsRemaining ;
55- private readonly Queue < TaskCompletionSourceWithCancellation < bool > > _waitingRequests = new Queue < TaskCompletionSourceWithCancellation < bool > > ( ) ;
56-
5752 // A connection-level error will abort any future operations.
5853 private Exception ? _abortException ;
5954
@@ -87,8 +82,6 @@ public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAutho
8782 string altUsedValue = altUsedDefaultPort ? authority . IdnHost : authority . IdnHost + ":" + authority . Port . ToString ( Globalization . CultureInfo . InvariantCulture ) ;
8883 _altUsedEncodedHeader = QPack . QPackEncoder . EncodeLiteralHeaderFieldWithoutNameReferenceToArray ( KnownHeaders . AltUsed . Name , altUsedValue ) ;
8984
90- _maximumRequestStreams = _requestStreamsRemaining = connection . GetRemoteAvailableBidirectionalStreamCount ( ) ;
91-
9285 // Errors are observed via Abort().
9386 _ = SendSettingsAsync ( ) ;
9487
@@ -166,54 +159,41 @@ public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req
166159 {
167160 Debug . Assert ( async ) ;
168161
169- // Wait for an available stream (based on QUIC MAX_STREAMS) if there isn't one available yet.
170-
171- TaskCompletionSourceWithCancellation < bool > ? waitForAvailableStreamTcs = null ;
172-
173- lock ( SyncObj )
174- {
175- long remaining = _requestStreamsRemaining ;
176-
177- if ( remaining > 0 )
178- {
179- _requestStreamsRemaining = remaining - 1 ;
180- }
181- else
182- {
183- waitForAvailableStreamTcs = new TaskCompletionSourceWithCancellation < bool > ( ) ;
184- _waitingRequests . Enqueue ( waitForAvailableStreamTcs ) ;
185- }
186- }
187-
188- if ( waitForAvailableStreamTcs != null )
189- {
190- await waitForAvailableStreamTcs . WaitWithCancellationAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
191- }
192-
193162 // Allocate an active request
194-
195163 QuicStream ? quicStream = null ;
196164 Http3RequestStream ? requestStream = null ;
165+ ValueTask waitTask = default ;
197166
198167 try
199168 {
200- lock ( SyncObj )
169+ while ( true )
201170 {
202- if ( _connection != null )
171+ lock ( SyncObj )
203172 {
204- quicStream = _connection . OpenBidirectionalStream ( ) ;
205- requestStream = new Http3RequestStream ( request , this , quicStream ) ;
206- _activeRequests . Add ( quicStream , requestStream ) ;
173+ if ( _connection == null )
174+ {
175+ break ;
176+ }
177+
178+ if ( _connection . GetRemoteAvailableBidirectionalStreamCount ( ) > 0 )
179+ {
180+ quicStream = _connection . OpenBidirectionalStream ( ) ;
181+ requestStream = new Http3RequestStream ( request , this , quicStream ) ;
182+ _activeRequests . Add ( quicStream , requestStream ) ;
183+ break ;
184+ }
185+ waitTask = _connection . WaitForAvailableBidirectionalStreamsAsync ( cancellationToken ) ;
207186 }
187+
188+ // Wait for an available stream (based on QUIC MAX_STREAMS) if there isn't one available yet.
189+ await waitTask . ConfigureAwait ( false ) ;
208190 }
209191
210192 if ( quicStream == null )
211193 {
212194 throw new HttpRequestException ( SR . net_http_request_aborted , null , RequestRetryType . RetryOnConnectionFailure ) ;
213195 }
214196
215- // 0-byte write to force QUIC to allocate a stream ID.
216- await quicStream . WriteAsync ( Array . Empty < byte > ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
217197 requestStream ! . StreamId = quicStream . StreamId ;
218198
219199 bool goAway ;
@@ -246,76 +226,6 @@ public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req
246226 }
247227 }
248228
249- /// <summary>
250- /// Waits for MAX_STREAMS to be raised by the server.
251- /// </summary>
252- private Task WaitForAvailableRequestStreamAsync ( CancellationToken cancellationToken )
253- {
254- TaskCompletionSourceWithCancellation < bool > tcs ;
255-
256- lock ( SyncObj )
257- {
258- long remaining = _requestStreamsRemaining ;
259-
260- if ( remaining > 0 )
261- {
262- _requestStreamsRemaining = remaining - 1 ;
263- return Task . CompletedTask ;
264- }
265-
266- tcs = new TaskCompletionSourceWithCancellation < bool > ( ) ;
267- _waitingRequests . Enqueue ( tcs ) ;
268- }
269-
270- // Note: cancellation on connection shutdown is handled in CancelWaiters.
271- return tcs . WaitWithCancellationAsync ( cancellationToken ) . AsTask ( ) ;
272- }
273-
274- /// <summary>
275- /// Cancels any waiting SendAsync calls.
276- /// </summary>
277- /// <remarks>Requires <see cref="SyncObj"/> to be held.</remarks>
278- private void CancelWaiters ( )
279- {
280- Debug . Assert ( Monitor . IsEntered ( SyncObj ) ) ;
281-
282- while ( _waitingRequests . TryDequeue ( out TaskCompletionSourceWithCancellation < bool > ? tcs ) )
283- {
284- tcs . TrySetException ( new HttpRequestException ( SR . net_http_request_aborted , null , RequestRetryType . RetryOnConnectionFailure ) ) ;
285- }
286- }
287-
288- // TODO: how do we get this event? -> HandleEventStreamsAvailable reports currently available Uni/Bi streams
289- private void OnMaximumStreamCountIncrease ( long newMaximumStreamCount )
290- {
291- lock ( SyncObj )
292- {
293- if ( newMaximumStreamCount <= _maximumRequestStreams )
294- {
295- return ;
296- }
297-
298- IncreaseRemainingStreamCount ( newMaximumStreamCount - _maximumRequestStreams ) ;
299- _maximumRequestStreams = newMaximumStreamCount ;
300- }
301- }
302-
303- private void IncreaseRemainingStreamCount ( long delta )
304- {
305- Debug . Assert ( Monitor . IsEntered ( SyncObj ) ) ;
306- Debug . Assert ( delta > 0 ) ;
307-
308- _requestStreamsRemaining += delta ;
309-
310- while ( _requestStreamsRemaining != 0 && _waitingRequests . TryDequeue ( out TaskCompletionSourceWithCancellation < bool > ? tcs ) )
311- {
312- if ( tcs . TrySetResult ( true ) )
313- {
314- -- _requestStreamsRemaining ;
315- }
316- }
317- }
318-
319229 /// <summary>
320230 /// Aborts the connection with an error.
321231 /// </summary>
@@ -358,7 +268,6 @@ internal Exception Abort(Exception abortException)
358268 _connectionClosedTask = _connection . CloseAsync ( ( long ) connectionResetErrorCode ) . AsTask ( ) ;
359269 }
360270
361- CancelWaiters ( ) ;
362271 CheckForShutdown ( ) ;
363272 }
364273
@@ -396,7 +305,6 @@ private void OnServerGoAway(long lastProcessedStreamId)
396305 }
397306 }
398307
399- CancelWaiters ( ) ;
400308 CheckForShutdown ( ) ;
401309 }
402310
@@ -414,8 +322,6 @@ public void RemoveStream(QuicStream stream)
414322 bool removed = _activeRequests . Remove ( stream ) ;
415323 Debug . Assert ( removed == true ) ;
416324
417- IncreaseRemainingStreamCount ( 1 ) ;
418-
419325 if ( ShuttingDown )
420326 {
421327 CheckForShutdown ( ) ;
0 commit comments