Skip to content

Commit 297aeef

Browse files
authored
HTTP/3: Remove TCS allocation with resettable value source (#38560)
1 parent 13a4608 commit 297aeef

File tree

1 file changed

+16
-12
lines changed

1 file changed

+16
-12
lines changed

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

+16-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net.Http;
88
using System.Net.Http.QPack;
99
using System.Runtime.CompilerServices;
10+
using System.Threading.Tasks.Sources;
1011
using Microsoft.AspNetCore.Connections;
1112
using Microsoft.AspNetCore.Connections.Features;
1213
using Microsoft.AspNetCore.Hosting.Server;
@@ -47,8 +48,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpH
4748
private int _totalParsedHeaderSize;
4849
private bool _isMethodConnect;
4950

50-
// TODO: Change to resetable ValueTask source
51-
private TaskCompletionSource? _appCompleted;
51+
private readonly ManualResetValueTaskSource<object?> _appCompletedTaskSource = new ManualResetValueTaskSource<object?>();
5252

5353
private StreamCompletionFlags _completionState;
5454
private readonly object _completionLock = new object();
@@ -70,8 +70,8 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpH
7070
public long StreamId => _streamIdFeature.StreamId;
7171

7272
public long StreamTimeoutTicks { get; set; }
73-
public bool IsReceivingHeader => _appCompleted == null; // TCS is assigned once headers are received
74-
public bool IsDraining => _appCompleted?.Task.IsCompleted ?? false; // Draining starts once app is complete
73+
public bool IsReceivingHeader => _requestHeaderParsingState <= RequestHeaderParsingState.Headers; // Assigned once headers are received
74+
public bool IsDraining => _appCompletedTaskSource.GetStatus() != ValueTaskSourceStatus.Pending; // Draining starts once app is complete
7575

7676
public bool IsRequestStream => true;
7777

@@ -87,7 +87,7 @@ public void Initialize(Http3StreamContext context)
8787
_streamIdFeature = _context.ConnectionFeatures.Get<IStreamIdFeature>()!;
8888
_streamAbortFeature = _context.ConnectionFeatures.Get<IStreamAbortFeature>()!;
8989

90-
_appCompleted = null;
90+
_appCompletedTaskSource.Reset();
9191
_isClosed = 0;
9292
_requestHeaderParsingState = default;
9393
_parsedPseudoHeaderFields = default;
@@ -403,8 +403,7 @@ private void CompleteStream(bool errored)
403403

404404
// Stream will be pooled after app completed.
405405
// Wait to signal app completed after any potential aborts on the stream.
406-
Debug.Assert(_appCompleted != null);
407-
_appCompleted.SetResult();
406+
_appCompletedTaskSource.SetResult(null);
408407
}
409408

410409
private bool TryClose()
@@ -492,8 +491,12 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
492491

493492
await Input.CompleteAsync();
494493

495-
var appCompleted = _appCompleted?.Task ?? Task.CompletedTask;
496-
if (!appCompleted.IsCompletedSuccessfully)
494+
// Once the header is finished being received then the app has started.
495+
var appCompletedTask = !IsReceivingHeader
496+
? new ValueTask(_appCompletedTaskSource, _appCompletedTaskSource.Version)
497+
: ValueTask.CompletedTask;
498+
499+
if (!appCompletedTask.IsCompletedSuccessfully)
497500
{
498501
// At this point in the stream's read-side is complete. However, with HTTP/3
499502
// the write-side of the stream can still be aborted by the client on request
@@ -529,7 +532,7 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
529532
}, this);
530533

531534
// Make sure application func is completed before completing writer.
532-
await appCompleted;
535+
await appCompletedTask;
533536
}
534537

535538
try
@@ -630,7 +633,7 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
630633
throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame);
631634
}
632635

633-
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
636+
if (_requestHeaderParsingState == RequestHeaderParsingState.Body)
634637
{
635638
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
636639
}
@@ -679,7 +682,7 @@ private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext>
679682
throw new Http3StreamErrorException(CoreStrings.HttpErrorMissingMandatoryPseudoHeaderFields, Http3ErrorCode.MessageError);
680683
}
681684

682-
_appCompleted = new TaskCompletionSource();
685+
_requestHeaderParsingState = RequestHeaderParsingState.Body;
683686
StreamTimeoutTicks = default;
684687
_context.StreamLifetimeHandler.OnStreamHeaderReceived(this);
685688

@@ -990,6 +993,7 @@ protected enum RequestHeaderParsingState
990993
Ready,
991994
PseudoHeaderFields,
992995
Headers,
996+
Body,
993997
Trailers
994998
}
995999

0 commit comments

Comments
 (0)