2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Concurrent ;
5
6
using System . Collections . Generic ;
6
7
using System . Diagnostics ;
7
8
using System . IO ;
14
15
using Microsoft . AspNetCore . Connections . Features ;
15
16
using Microsoft . AspNetCore . Hosting . Server ;
16
17
using Microsoft . AspNetCore . Http . Features ;
18
+ using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http ;
17
19
using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Infrastructure ;
18
20
using Microsoft . Extensions . Logging ;
19
21
20
22
namespace Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http3
21
23
{
22
- internal class Http3Connection : ITimeoutHandler
24
+ internal class Http3Connection : ITimeoutHandler , IHttp3StreamLifetimeHandler
23
25
{
24
26
internal readonly Dictionary < long , Http3Stream > _streams = new Dictionary < long , Http3Stream > ( ) ;
27
+ internal readonly ConcurrentQueue < IHttp3Stream > _startingStreams = new ConcurrentQueue < IHttp3Stream > ( ) ;
25
28
26
29
private long _highestOpenedStreamId ;
27
30
private readonly object _sync = new object ( ) ;
@@ -84,9 +87,6 @@ public async Task ProcessStreamsAsync<TContext>(IHttpApplication<TContext> httpA
84
87
{
85
88
try
86
89
{
87
- // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
88
- _timeoutControl . Initialize ( _systemClock . UtcNowTicks ) ;
89
-
90
90
var connectionHeartbeatFeature = _context . ConnectionFeatures . Get < IConnectionHeartbeatFeature > ( ) ;
91
91
var connectionLifetimeNotificationFeature = _context . ConnectionFeatures . Get < IConnectionLifetimeNotificationFeature > ( ) ;
92
92
@@ -200,11 +200,61 @@ public void Tick()
200
200
201
201
// It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
202
202
var now = _systemClock . UtcNowUnsynchronized ;
203
- _timeoutControl . Tick ( now ) ;
203
+
204
+ UpdateStartingStreams ( now . Ticks ) ;
204
205
205
206
// TODO cancel process stream loop to update logic.
206
207
}
207
208
209
+ private void UpdateStartingStreams ( long now )
210
+ {
211
+ IHttp3Stream ? firstRequedStream = null ;
212
+
213
+ while ( _startingStreams . TryDequeue ( out var stream ) )
214
+ {
215
+ if ( stream == firstRequedStream )
216
+ {
217
+ // We've checked every stream that was in _startingStreams.
218
+ _startingStreams . Enqueue ( stream ) ;
219
+ break ;
220
+ }
221
+
222
+ if ( stream . HasStarted )
223
+ {
224
+ continue ;
225
+ }
226
+
227
+ if ( stream . StartExpirationTicks == default )
228
+ {
229
+ stream . StartExpirationTicks = now + _context . ServiceContext . ServerOptions . Limits . RequestHeadersTimeout . Ticks >= 0
230
+ ? now + _context . ServiceContext . ServerOptions . Limits . RequestHeadersTimeout . Ticks
231
+ : long . MaxValue ;
232
+ }
233
+
234
+ if ( stream . StartExpirationTicks < now )
235
+ {
236
+ if ( stream . IsRequestStream )
237
+ {
238
+ stream . Abort ( new ConnectionAbortedException ( CoreStrings . BadRequest_RequestHeadersTimeout ) , Http3ErrorCode . RequestRejected ) ;
239
+ }
240
+ else
241
+ {
242
+ stream . Abort ( new ConnectionAbortedException ( CoreStrings . Http3ControlStreamHeaderTimeout ) , Http3ErrorCode . StreamCreationError ) ;
243
+ }
244
+ }
245
+ else
246
+ {
247
+ if ( firstRequedStream == null )
248
+ {
249
+ firstRequedStream = stream ;
250
+ }
251
+
252
+ _startingStreams . Enqueue ( stream ) ;
253
+ }
254
+ }
255
+ }
256
+
257
+
208
258
public void OnTimeout ( TimeoutReason reason )
209
259
{
210
260
// In the cases that don't log directly here, we expect the setter of the timeout to also be the input
@@ -213,13 +263,11 @@ public void OnTimeout(TimeoutReason reason)
213
263
// TODO what timeouts should we handle here? Is keep alive something we should care about?
214
264
switch ( reason )
215
265
{
216
- case TimeoutReason . KeepAlive :
217
- SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
218
- break ;
219
266
case TimeoutReason . TimeoutFeature :
220
267
SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
221
268
break ;
222
- case TimeoutReason . RequestHeaders :
269
+ case TimeoutReason . RequestHeaders : // Request header timeout is handled in starting stream queue
270
+ case TimeoutReason . KeepAlive : // Keep-alive is handled by msquic
223
271
case TimeoutReason . ReadDataRate :
224
272
case TimeoutReason . WriteDataRate :
225
273
case TimeoutReason . RequestBodyDrain :
@@ -245,8 +293,6 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
245
293
// TODO should we await the control stream task?
246
294
var controlTask = CreateControlStream ( application ) ;
247
295
248
- _timeoutControl . SetTimeout ( Limits . KeepAliveTimeout . Ticks , TimeoutReason . KeepAlive ) ;
249
-
250
296
try
251
297
{
252
298
while ( _isClosed == 0 )
@@ -277,29 +323,32 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
277
323
streamContext . LocalEndPoint as IPEndPoint ,
278
324
streamContext . RemoteEndPoint as IPEndPoint ,
279
325
streamContext . Transport ,
326
+ this ,
280
327
streamContext ,
281
328
_serverSettings ) ;
282
- httpConnectionContext . TimeoutControl = _context . TimeoutControl ;
329
+ httpConnectionContext . TimeoutControl = _timeoutControl ;
283
330
284
331
if ( ! quicStreamFeature . CanWrite )
285
332
{
286
333
// Unidirectional stream
287
- var stream = new Http3ControlStream < TContext > ( application , this , httpConnectionContext ) ;
334
+ var stream = new Http3ControlStream < TContext > ( application , httpConnectionContext ) ;
288
335
ThreadPool . UnsafeQueueUserWorkItem ( stream , preferLocal : false ) ;
289
336
}
290
337
else
291
338
{
339
+ // Request stream
292
340
var streamId = streamIdFeature . StreamId ;
293
341
294
342
UpdateHighestStreamId ( streamId ) ;
295
343
296
- var http3Stream = new Http3Stream < TContext > ( application , this , httpConnectionContext ) ;
344
+ var http3Stream = new Http3Stream < TContext > ( application , httpConnectionContext ) ;
297
345
var stream = http3Stream ;
298
346
lock ( _streams )
299
347
{
300
348
_activeRequestCount ++ ;
301
349
_streams [ streamId ] = http3Stream ;
302
350
}
351
+
303
352
KestrelEventSource . Log . RequestQueuedStart ( stream , AspNetCore . Http . HttpProtocol . Http3 ) ;
304
353
ThreadPool . UnsafeQueueUserWorkItem ( stream , preferLocal : false ) ;
305
354
}
@@ -363,8 +412,6 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
363
412
{
364
413
await _streamCompletionAwaitable ;
365
414
}
366
-
367
- _timeoutControl . CancelTimeout ( ) ;
368
415
}
369
416
catch
370
417
{
@@ -410,17 +457,6 @@ private void UpdateConnectionState()
410
457
SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
411
458
}
412
459
}
413
- else
414
- {
415
- // TODO should keep-alive timeout be a thing for HTTP/3? MsQuic currently tracks this for us?
416
- if ( _timeoutControl . TimerReason == TimeoutReason . None )
417
- {
418
- _timeoutControl . SetTimeout ( Limits . KeepAliveTimeout . Ticks , TimeoutReason . KeepAlive ) ;
419
- }
420
-
421
- // Only reason should be keep-alive.
422
- Debug . Assert ( _timeoutControl . TimerReason == TimeoutReason . KeepAlive ) ;
423
- }
424
460
}
425
461
}
426
462
@@ -450,11 +486,12 @@ private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<T
450
486
streamContext . LocalEndPoint as IPEndPoint ,
451
487
streamContext . RemoteEndPoint as IPEndPoint ,
452
488
streamContext . Transport ,
489
+ this ,
453
490
streamContext ,
454
491
_serverSettings ) ;
455
- httpConnectionContext . TimeoutControl = _context . TimeoutControl ;
492
+ httpConnectionContext . TimeoutControl = _timeoutControl ;
456
493
457
- return new Http3ControlStream < TContext > ( application , this , httpConnectionContext ) ;
494
+ return new Http3ControlStream < TContext > ( application , httpConnectionContext ) ;
458
495
}
459
496
460
497
private ValueTask < FlushResult > SendGoAway ( long id )
@@ -469,30 +506,7 @@ private ValueTask<FlushResult> SendGoAway(long id)
469
506
return new ValueTask < FlushResult > ( ) ;
470
507
}
471
508
472
- public void ApplyMaxHeaderListSize ( long value )
473
- {
474
- }
475
-
476
- internal void ApplyBlockedStream ( long value )
477
- {
478
- }
479
-
480
- internal void ApplyMaxTableCapacity ( long value )
481
- {
482
- }
483
-
484
- internal void RemoveStream ( long streamId )
485
- {
486
- lock ( _streams )
487
- {
488
- _activeRequestCount -- ;
489
- _streams . Remove ( streamId ) ;
490
- }
491
-
492
- _streamCompletionAwaitable . Complete ( ) ;
493
- }
494
-
495
- public bool SetInboundControlStream ( Http3ControlStream stream )
509
+ public bool OnInboundControlStream ( Http3ControlStream stream )
496
510
{
497
511
lock ( _sync )
498
512
{
@@ -505,7 +519,7 @@ public bool SetInboundControlStream(Http3ControlStream stream)
505
519
}
506
520
}
507
521
508
- public bool SetInboundEncoderStream ( Http3ControlStream stream )
522
+ public bool OnInboundEncoderStream ( Http3ControlStream stream )
509
523
{
510
524
lock ( _sync )
511
525
{
@@ -518,7 +532,7 @@ public bool SetInboundEncoderStream(Http3ControlStream stream)
518
532
}
519
533
}
520
534
521
- public bool SetInboundDecoderStream ( Http3ControlStream stream )
535
+ public bool OnInboundDecoderStream ( Http3ControlStream stream )
522
536
{
523
537
lock ( _sync )
524
538
{
@@ -531,6 +545,47 @@ public bool SetInboundDecoderStream(Http3ControlStream stream)
531
545
}
532
546
}
533
547
548
+ public void OnStreamCompleted ( IHttp3Stream stream )
549
+ {
550
+ lock ( _streams )
551
+ {
552
+ _activeRequestCount -- ;
553
+ _streams . Remove ( stream . StreamId ) ;
554
+ }
555
+
556
+ _streamCompletionAwaitable . Complete ( ) ;
557
+ }
558
+
559
+ public void OnStreamConnectionError ( Http3ConnectionErrorException ex )
560
+ {
561
+ Log . Http3ConnectionError ( ConnectionId , ex ) ;
562
+ Abort ( new ConnectionAbortedException ( ex . Message , ex ) , ex . ErrorCode ) ;
563
+ }
564
+
565
+ public void OnInboundControlStreamSetting ( Http3SettingType type , long value )
566
+ {
567
+ switch ( type )
568
+ {
569
+ case Http3SettingType . QPackMaxTableCapacity :
570
+ break ;
571
+ case Http3SettingType . MaxFieldSectionSize :
572
+ break ;
573
+ case Http3SettingType . QPackBlockedStreams :
574
+ break ;
575
+ default :
576
+ throw new InvalidOperationException ( "Unexpected setting: " + type ) ;
577
+ }
578
+ }
579
+
580
+ public void OnStreamStarting ( IHttp3Stream stream )
581
+ {
582
+ _startingStreams . Enqueue ( stream ) ;
583
+ }
584
+
585
+ public void OnStreamStarted ( IHttp3Stream stream )
586
+ {
587
+ }
588
+
534
589
private static class GracefulCloseInitiator
535
590
{
536
591
public const int None = 0 ;
0 commit comments