-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Stop doing sync over async in Produce100Continue #31650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5156206
5c25fa2
0503f75
8d8a146
70e8ee1
a10c9d9
f91acea
b93be28
a99aea8
b147ac7
a9a5619
361f009
7893fbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,7 @@ public override async ValueTask<ReadResult> ReadAsyncInternal(CancellationToken | |
KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); | ||
} | ||
|
||
TryStart(); | ||
await TryStartAsync(); | ||
|
||
// The while(true) loop is required because the Http1 connection calls CancelPendingRead to unblock | ||
// the call to StartTimingReadAsync to check if the request timed out. | ||
|
@@ -132,7 +132,7 @@ public override bool TryReadInternal(out ReadResult readResult) | |
KestrelBadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); | ||
} | ||
|
||
TryStart(); | ||
TryStartAsync(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this a behavior change to go from blocking to fire-and-forget? Can TryStartAsync throw asynchronously like if the client has disconnected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add an assert. I dislike the fire and forget as well unless we have an explicit check that it's always completed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops, plus a10c9d9 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love that bug 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I totally agree. I also dislike fire-and-forget with a passion, but what's even worse is blocking on I/O. This isn't the only place we don't await output-writing Now unlike with HTTP/2 where you could conceive of a scenario where output backpressure has built up prior to sending a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense to me - if it matches what we do for |
||
|
||
// The while(true) because we don't want to return a canceled ReadResult if the user themselves didn't cancel it. | ||
while (true) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,11 +66,21 @@ public virtual ValueTask CompleteAsync(Exception? exception) | |
|
||
public virtual Task ConsumeAsync() | ||
{ | ||
TryStart(); | ||
Task startTask = TryStartAsync(); | ||
if (!startTask.IsCompletedSuccessfully) | ||
{ | ||
return ConsumeAwaited(startTask); | ||
} | ||
|
||
return OnConsumeAsync(); | ||
} | ||
|
||
private async Task ConsumeAwaited(Task startTask) | ||
{ | ||
await startTask; | ||
await OnConsumeAsync(); | ||
} | ||
|
||
public virtual ValueTask StopAsync() | ||
{ | ||
TryStop(); | ||
|
@@ -93,20 +103,22 @@ public virtual void Reset() | |
_examinedUnconsumedBytes = 0; | ||
} | ||
|
||
protected void TryProduceContinue() | ||
protected ValueTask<FlushResult> TryProduceContinueAsync() | ||
{ | ||
if (_send100Continue) | ||
{ | ||
_context.HttpResponseControl.ProduceContinue(); | ||
_send100Continue = false; | ||
return _context.HttpResponseControl.ProduceContinueAsync(); | ||
} | ||
|
||
return default; | ||
} | ||
|
||
protected void TryStart() | ||
protected Task TryStartAsync() | ||
{ | ||
if (_context.HasStartedConsumingRequestBody) | ||
{ | ||
return; | ||
return Task.CompletedTask; | ||
} | ||
|
||
OnReadStarting(); | ||
|
@@ -128,7 +140,7 @@ protected void TryStart() | |
} | ||
} | ||
|
||
OnReadStarted(); | ||
return OnReadStartedAsync(); | ||
} | ||
|
||
protected void TryStop() | ||
|
@@ -165,8 +177,9 @@ protected virtual void OnReadStarting() | |
{ | ||
} | ||
|
||
protected virtual void OnReadStarted() | ||
protected virtual Task OnReadStartedAsync() | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
protected void AddAndCheckObservedBytes(long observedBytes) | ||
|
@@ -183,7 +196,15 @@ protected ValueTask<ReadResult> StartTimingReadAsync(ValueTask<ReadResult> readA | |
{ | ||
if (!readAwaitable.IsCompleted) | ||
{ | ||
TryProduceContinue(); | ||
ValueTask<FlushResult> continueTask = TryProduceContinueAsync(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we indicate that the ValueTask result has been consumed? @stephentoub submitted a PR to do that for historical code: #31221 if (!continueTask.IsCompletedSuccessfully)
{
return StartTimingReadAwaited(continueTask, readAwaitable, cancellationToken);
}
else
{
continueTask.GetAwaiter().GetResult();
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm. I'd have expected the code in the PR to trigger CA2012, providing the same feedback as James. Were no warnings issued for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your .editconfig changes are still there: https://github.com/dotnet/aspnetcore/blob/main/.editorconfig I'm not familiar with configuring analyzers in editconfigs or CA2021. I'm not sure why it wasn't prompted. |
||
if (!continueTask.IsCompletedSuccessfully) | ||
{ | ||
return StartTimingReadAwaited(continueTask, readAwaitable, cancellationToken); | ||
} | ||
else | ||
{ | ||
continueTask.GetAwaiter().GetResult(); | ||
} | ||
|
||
if (_timingEnabled) | ||
{ | ||
|
@@ -195,6 +216,19 @@ protected ValueTask<ReadResult> StartTimingReadAsync(ValueTask<ReadResult> readA | |
return readAwaitable; | ||
} | ||
|
||
protected async ValueTask<ReadResult> StartTimingReadAwaited(ValueTask<FlushResult> continueTask, ValueTask<ReadResult> readAwaitable, CancellationToken cancellationToken) | ||
{ | ||
await continueTask; | ||
|
||
if (_timingEnabled) | ||
{ | ||
_backpressure = true; | ||
_context.TimeoutControl.StartTimingRead(); | ||
} | ||
|
||
return await readAwaitable; | ||
} | ||
|
||
protected void CountBytesRead(long bytesInReadResult) | ||
{ | ||
var numFirstSeenBytes = bytesInReadResult - _alreadyTimedBytes; | ||
|
Uh oh!
There was an error while loading. Please reload this page.