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
- internal readonly Dictionary < long , Http3Stream > _streams = new Dictionary < long , Http3Stream > ( ) ;
26
+ internal readonly Dictionary < long , IHttp3Stream > _streams = new Dictionary < long , IHttp3Stream > ( ) ;
25
27
26
28
private long _highestOpenedStreamId ;
27
29
private readonly object _sync = new object ( ) ;
@@ -84,9 +86,6 @@ public async Task ProcessStreamsAsync<TContext>(IHttpApplication<TContext> httpA
84
86
{
85
87
try
86
88
{
87
- // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
88
- _timeoutControl . Initialize ( _systemClock . UtcNowTicks ) ;
89
-
90
89
var connectionHeartbeatFeature = _context . ConnectionFeatures . Get < IConnectionHeartbeatFeature > ( ) ;
91
90
var connectionLifetimeNotificationFeature = _context . ConnectionFeatures . Get < IConnectionLifetimeNotificationFeature > ( ) ;
92
91
@@ -198,11 +197,42 @@ public void Tick()
198
197
return ;
199
198
}
200
199
201
- // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
202
- var now = _systemClock . UtcNowUnsynchronized ;
203
- _timeoutControl . Tick ( now ) ;
200
+ UpdateStartingStreams ( ) ;
201
+ }
202
+
203
+ private void UpdateStartingStreams ( )
204
+ {
205
+ var now = _systemClock . UtcNow . Ticks ;
206
+
207
+ lock ( _streams )
208
+ {
209
+ foreach ( var stream in _streams . Values )
210
+ {
211
+ if ( stream . ReceivedHeader )
212
+ {
213
+ continue ;
214
+ }
215
+
216
+ if ( stream . HeaderTimeoutTicks == default )
217
+ {
218
+ // On expiration overflow, use max value.
219
+ var expirationTicks = now + _context . ServiceContext . ServerOptions . Limits . RequestHeadersTimeout . Ticks ;
220
+ stream . HeaderTimeoutTicks = expirationTicks >= 0 ? expirationTicks : long . MaxValue ;
221
+ }
204
222
205
- // TODO cancel process stream loop to update logic.
223
+ if ( stream . HeaderTimeoutTicks < now )
224
+ {
225
+ if ( stream . IsRequestStream )
226
+ {
227
+ stream . Abort ( new ConnectionAbortedException ( CoreStrings . BadRequest_RequestHeadersTimeout ) , Http3ErrorCode . RequestRejected ) ;
228
+ }
229
+ else
230
+ {
231
+ stream . Abort ( new ConnectionAbortedException ( CoreStrings . Http3ControlStreamHeaderTimeout ) , Http3ErrorCode . StreamCreationError ) ;
232
+ }
233
+ }
234
+ }
235
+ }
206
236
}
207
237
208
238
public void OnTimeout ( TimeoutReason reason )
@@ -213,13 +243,11 @@ public void OnTimeout(TimeoutReason reason)
213
243
// TODO what timeouts should we handle here? Is keep alive something we should care about?
214
244
switch ( reason )
215
245
{
216
- case TimeoutReason . KeepAlive :
217
- SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
218
- break ;
219
246
case TimeoutReason . TimeoutFeature :
220
247
SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
221
248
break ;
222
- case TimeoutReason . RequestHeaders :
249
+ case TimeoutReason . RequestHeaders : // Request header timeout is handled in starting stream queue
250
+ case TimeoutReason . KeepAlive : // Keep-alive is handled by msquic
223
251
case TimeoutReason . ReadDataRate :
224
252
case TimeoutReason . WriteDataRate :
225
253
case TimeoutReason . RequestBodyDrain :
@@ -245,8 +273,6 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
245
273
// TODO should we await the control stream task?
246
274
var controlTask = CreateControlStream ( application ) ;
247
275
248
- _timeoutControl . SetTimeout ( Limits . KeepAliveTimeout . Ticks , TimeoutReason . KeepAlive ) ;
249
-
250
276
try
251
277
{
252
278
while ( _isClosed == 0 )
@@ -277,29 +303,36 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
277
303
streamContext . LocalEndPoint as IPEndPoint ,
278
304
streamContext . RemoteEndPoint as IPEndPoint ,
279
305
streamContext . Transport ,
306
+ this ,
280
307
streamContext ,
281
308
_serverSettings ) ;
282
309
httpConnectionContext . TimeoutControl = _context . TimeoutControl ;
283
310
311
+ var streamId = streamIdFeature . StreamId ;
312
+
284
313
if ( ! quicStreamFeature . CanWrite )
285
314
{
286
315
// Unidirectional stream
287
- var stream = new Http3ControlStream < TContext > ( application , this , httpConnectionContext ) ;
316
+ var stream = new Http3ControlStream < TContext > ( application , httpConnectionContext ) ;
317
+ lock ( _streams )
318
+ {
319
+ _streams [ streamId ] = stream ;
320
+ }
321
+
288
322
ThreadPool . UnsafeQueueUserWorkItem ( stream , preferLocal : false ) ;
289
323
}
290
324
else
291
325
{
292
- var streamId = streamIdFeature . StreamId ;
293
-
326
+ // Request stream
294
327
UpdateHighestStreamId ( streamId ) ;
295
328
296
- var http3Stream = new Http3Stream < TContext > ( application , this , httpConnectionContext ) ;
297
- var stream = http3Stream ;
329
+ var stream = new Http3Stream < TContext > ( application , httpConnectionContext ) ;
298
330
lock ( _streams )
299
331
{
300
332
_activeRequestCount ++ ;
301
- _streams [ streamId ] = http3Stream ;
333
+ _streams [ streamId ] = stream ;
302
334
}
335
+
303
336
KestrelEventSource . Log . RequestQueuedStart ( stream , AspNetCore . Http . HttpProtocol . Http3 ) ;
304
337
ThreadPool . UnsafeQueueUserWorkItem ( stream , preferLocal : false ) ;
305
338
}
@@ -363,8 +396,6 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
363
396
{
364
397
await _streamCompletionAwaitable ;
365
398
}
366
-
367
- _timeoutControl . CancelTimeout ( ) ;
368
399
}
369
400
catch
370
401
{
@@ -410,17 +441,6 @@ private void UpdateConnectionState()
410
441
SendGoAway ( GetHighestStreamId ( ) ) . Preserve ( ) ;
411
442
}
412
443
}
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
444
}
425
445
}
426
446
@@ -450,11 +470,12 @@ private async ValueTask<Http3ControlStream> CreateNewUnidirectionalStreamAsync<T
450
470
streamContext . LocalEndPoint as IPEndPoint ,
451
471
streamContext . RemoteEndPoint as IPEndPoint ,
452
472
streamContext . Transport ,
473
+ this ,
453
474
streamContext ,
454
475
_serverSettings ) ;
455
476
httpConnectionContext . TimeoutControl = _context . TimeoutControl ;
456
477
457
- return new Http3ControlStream < TContext > ( application , this , httpConnectionContext ) ;
478
+ return new Http3ControlStream < TContext > ( application , httpConnectionContext ) ;
458
479
}
459
480
460
481
private ValueTask < FlushResult > SendGoAway ( long id )
@@ -466,33 +487,10 @@ private ValueTask<FlushResult> SendGoAway(long id)
466
487
return OutboundControlStream . SendGoAway ( id ) ;
467
488
}
468
489
}
469
- return new ValueTask < FlushResult > ( ) ;
490
+ return default ;
470
491
}
471
492
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 )
493
+ public bool OnInboundControlStream ( Http3ControlStream stream )
496
494
{
497
495
lock ( _sync )
498
496
{
@@ -505,7 +503,7 @@ public bool SetInboundControlStream(Http3ControlStream stream)
505
503
}
506
504
}
507
505
508
- public bool SetInboundEncoderStream ( Http3ControlStream stream )
506
+ public bool OnInboundEncoderStream ( Http3ControlStream stream )
509
507
{
510
508
lock ( _sync )
511
509
{
@@ -518,7 +516,7 @@ public bool SetInboundEncoderStream(Http3ControlStream stream)
518
516
}
519
517
}
520
518
521
- public bool SetInboundDecoderStream ( Http3ControlStream stream )
519
+ public bool OnInboundDecoderStream ( Http3ControlStream stream )
522
520
{
523
521
lock ( _sync )
524
522
{
@@ -531,6 +529,38 @@ public bool SetInboundDecoderStream(Http3ControlStream stream)
531
529
}
532
530
}
533
531
532
+ public void OnStreamCompleted ( IHttp3Stream stream )
533
+ {
534
+ lock ( _streams )
535
+ {
536
+ _activeRequestCount -- ;
537
+ _streams . Remove ( stream . StreamId ) ;
538
+ }
539
+
540
+ _streamCompletionAwaitable . Complete ( ) ;
541
+ }
542
+
543
+ public void OnStreamConnectionError ( Http3ConnectionErrorException ex )
544
+ {
545
+ Log . Http3ConnectionError ( ConnectionId , ex ) ;
546
+ Abort ( new ConnectionAbortedException ( ex . Message , ex ) , ex . ErrorCode ) ;
547
+ }
548
+
549
+ public void OnInboundControlStreamSetting ( Http3SettingType type , long value )
550
+ {
551
+ switch ( type )
552
+ {
553
+ case Http3SettingType . QPackMaxTableCapacity :
554
+ break ;
555
+ case Http3SettingType . MaxFieldSectionSize :
556
+ break ;
557
+ case Http3SettingType . QPackBlockedStreams :
558
+ break ;
559
+ default :
560
+ throw new InvalidOperationException ( "Unexpected setting: " + type ) ;
561
+ }
562
+ }
563
+
534
564
private static class GracefulCloseInitiator
535
565
{
536
566
public const int None = 0 ;
0 commit comments