Skip to content
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

Stop doing sync over async in Produce100Continue #31650

Merged
merged 13 commits into from
Apr 13, 2021
Merged

Conversation

wtgodbe
Copy link
Member

@wtgodbe wtgodbe commented Apr 9, 2021

Fixes #31225

Draft with a first-pass at fixing the issue.

@wtgodbe wtgodbe requested a review from halter73 April 9, 2021 20:05
@ghost ghost added the area-runtime label Apr 9, 2021
@wtgodbe wtgodbe marked this pull request as ready for review April 9, 2021 22:40
TryStart();
TryStartAsync();
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Write100ContinueAsync() could realistically return an incomplete ValueTask this is a change in behavior. Not sure how that could ever happen though. If it could happen, not blocking is waaay better than block because at least you return the thread to the pool. TryStartAsync should never be able to throw asynchronously because it's a write without a canceled token.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, plus a10c9d9

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that bug 😄

Copy link
Member

@halter73 halter73 Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike the fire and forget

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 ValueTasks we expect to be usually completed.

Now unlike with HTTP/2 where you could conceive of a scenario where output backpressure has built up prior to sending a WINDOW_UPDATE or RST_STREAM, it's hard to come up with one for an HTTP/1.1 "100 continue" response. That said, it's not a mathematical invariant. I could write a unit test with a custom transport and write buffering disabled to force it. For that reason, I don't think we should add this Debug.Assert like we don't for WINDOW_UPDATE or RST_STREAM. If we think it's worthwhile we can store the task and await it in ConsumeAsync(), but I don't think that's necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me - if it matches what we do for RST_STREAM and WINDOW_UPDATE seems like we can remove the assert here

@wtgodbe
Copy link
Member Author

wtgodbe commented Apr 12, 2021

Got what looks to be a flaky failure in a selenium test, InputDateInteractsWithEditContext_NonNullableDateTime. CC @captainsafia since you're on build ops

Other than that, any other concerns @Tratcher @davidfowl?

OpenQA.Selenium.BrowserAssertFailedException : Xunit.Sdk.EqualException: Assert.Equal() Failure\r\nExpected: modified valid\r\nActual: modified invalid\r\n at Xunit.Assert.Equal[T](T expected, T actual, IEqualityComparer1 comparer) in C:\Dev\xunit\xunit\src\xunit.assert\Asserts\EqualityAsserts.cs:line 40\r\n at Microsoft.AspNetCore.E2ETesting.WaitAssert.<>c__DisplayClass17_0.b__0() in /_/src/Shared/E2ETesting/WaitAssert.cs:line 83\r\n at Microsoft.AspNetCore.E2ETesting.WaitAssert.<>c__DisplayClass18_01.b__0(IWebDriver ) in //src/Shared/E2ETesting/WaitAssert.cs:line 109\r\nScreen shot captured at 'D:\workspace_work\1\s\artifacts\TestResults\Release\Microsoft.AspNetCore.Components.E2ETests\bf12e230a077482c93a44465ccda36f2.png'\r\nEncountered browser errors\r\n[2021-04-12T17:01:25Z] [Info] http://127.0.0.1:53621/subdir/_framework/blazor.server.js 0:31687 "[2021-04-12T17:01:25.992Z] Information: Normalizing '_blazor' to 'http://127.0.0.1:53621/subdir/_blazor'."\r\n[2021-04-12T17:01:25Z] [Info] http://127.0.0.1:53621/subdir 45:16 "Blazor server-side"\r\n[2021-04-12T17:01:26Z] [Info] http://127.0.0.1:53621/subdir/_framework/blazor.server.js 0:31687 "[2021-04-12T17:01:25.998Z] Information: WebSocket connected to ws://127.0.0.1:53621/subdir/_blazor?id=foJb3ozHB7bXxtdKAJ8Kbg."\r\n[2021-04-12T17:01:27Z] [Info] http://127.0.0.1:53621/subdir/_framework/blazor.server.js 0:31687 "[2021-04-12T17:01:27.398Z] Information: Normalizing '_blazor' to 'http://127.0.0.1:53621/subdir/_blazor'."\r\n[2021-04-12T17:01:27Z] [Info] http://127.0.0.1:53621/subdir 45:16 "Blazor server-side"\r\n[2021-04-12T17:01:27Z] [Info] http://127.0.0.1:53621/subdir/_framework/blazor.server.js 0:31687 "[2021-04-12T17:01:27.408Z] Information: WebSocket connected to ws://127.0.0.1:53621/subdir/_blazor?id=n_74YY7aNbRQ53NAUSdGuQ."Page content:\r\n\r\n \r\n <title>Basic test app</title>\r\n \r\n \r\n \r\n\r\n \r\n \r\n \r\n\r\n\r\n

\r\n Select test:\r\n Choose...\r\n After-render interop component\r\n Auth cases\r\n bind cases\r\n Component ref component\r\n

Comment on lines 143 to 149
Task readTask = OnReadStartedAsync();
if (!readTask.IsCompletedSuccessfully)
{
return readTask;
}

return Task.CompletedTask;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not:

Suggested change
Task readTask = OnReadStartedAsync();
if (!readTask.IsCompletedSuccessfully)
{
return readTask;
}
return Task.CompletedTask;
return OnReadStartedAsync();

If the OnReadStartedAsync task is complete then return just return it. And if it is not complete then also just return it...

@@ -183,7 +202,11 @@ protected ValueTask<ReadResult> StartTimingReadAsync(ValueTask<ReadResult> readA
{
if (!readAwaitable.IsCompleted)
{
TryProduceContinue();
ValueTask<FlushResult> continueTask = TryProduceContinueAsync();
Copy link
Member

Choose a reason for hiding this comment

The 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();
}

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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.

@wtgodbe
Copy link
Member Author

wtgodbe commented Apr 13, 2021

@JamesNK @halter73 just pushed a9a5619 to respond to feedback

@wtgodbe
Copy link
Member Author

wtgodbe commented Apr 13, 2021

Got a real test failure in Microsoft.AspNetCore.Server.Kestrel.Core.Tests.Http3StreamTests.ContentLength_Received_NoDataFrames_Reset

Assert.Equal() Failure
Expected: ProtocolError
Actual: 0

Looking

@jkotalik
Copy link
Contributor

I think it isn't you: #31743 (comment)

@jkotalik
Copy link
Contributor

@JamesNK should we just disable in this PR?

@JamesNK
Copy link
Member

JamesNK commented Apr 13, 2021

Issue: #31777

Add skip or quarantine back in this PR.

@wtgodbe
Copy link
Member Author

wtgodbe commented Apr 13, 2021

Done

@wtgodbe
Copy link
Member Author

wtgodbe commented Apr 13, 2021

@JamesNK any other concerns?

@wtgodbe wtgodbe merged commit 686da2e into main Apr 13, 2021
@wtgodbe wtgodbe deleted the wtgodbe/100Continue branch April 13, 2021 22:27
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix expect 100 continue logic in Kestrel to avoid doing sync over async
8 participants