-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Make StartAsync not throw if we haven't started the response #8199
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
Conversation
@anurse test discovery issue?
|
Didn't realize this was failing builds, should be fixed with #8201 |
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
/azp run AspNetCore-helix-test |
Azure Pipelines successfully started running 1 pipeline(s). |
@JamesNK and @JunTaoLuo are you okay with the restrictions on StartAsync that I listed above? After calling StartAsync, don't use the memory that was obtained before calling StartAsync. |
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
@@ -1066,7 +1072,7 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) | |||
{ | |||
_keepAlive = false; | |||
} | |||
else if (appCompleted || !_canWriteResponseBody) | |||
else if ((appCompleted || !_canWriteResponseBody) && !_hasAdvanced) // Avoid setting contentLength of 0 if we wrote data before calling CreateResponseHeaders |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if appCompleted && _hasAdvanced then we can set ContentLength to a specific value and avoid chunking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I was thinking about doing this in this PR but wasn't a fan. You would need to peek into the number of advanced bytes inside of the Http1OutputProducer which isn't clean. I'd prefer to do it in a separate pr.
@jkotalik Yes we are fine with that restriction, We currently only call GetSpan after calling StartAsync. We plan to remove the StartAsync calls once this PR is merged so the case of calling GetSpan/GetMemory before StartAsync will not occur. |
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/test/InMemory.FunctionalTests/ChunkedResponseTests.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs
Outdated
Show resolved
Hide resolved
src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs
Outdated
Show resolved
Hide resolved
I'm pretty sure that is fine. I'll take a closer look tomorrow. |
@JamesNK @JunTaoLuo the only thing I'm concerned about is how you mentioned that you wanted to avoid calling StartAsync to have buffering for free so you can modify headers later. It may cause issues if you call StartAsync later, putting you in a weird state. |
We wouldn't call |
Or did you mean something else? |
e75165f
to
da8b6ed
Compare
// If we are writing with GetMemory/Advance before calling StartAsync, assume we can write and throw away contents if we can't. | ||
private bool _canWriteResponseBody = true; | ||
private bool _hasAdvanced; | ||
private bool _isLeasedMemoryInvalid = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should _hasAdvanced and _isLeasedMemoryInvalid be reset between requests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a regression test that will fail if we forgot to reset?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya caught me.
🆙 📅 |
@@ -49,7 +49,6 @@ public static async Task EchoAppPipeWriter(HttpContext httpContext) | |||
if (buffer.Length > 0) | |||
{ | |||
await request.Body.ReadUntilEndAsync(buffer).DefaultTimeout(); | |||
await response.StartAsync(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should have unrelated tests hit the inefficient code path. Tests like ChunkGetMemoryAndWriteWithoutStart should be sufficient.
@@ -534,6 +579,8 @@ public async Task ChunksWithGetMemoryWithInitialFlushWorks() | |||
{ | |||
var response = httpContext.Response; | |||
|
|||
await response.StartAsync(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old version of this test would still pass, right?
await response.StartAsync(); | ||
|
||
var memory = response.BodyWriter.GetMemory(); | ||
length.Value = memory.Length - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: if length was a normal int, this test would still work since the closure would box it.
@@ -3088,7 +3174,7 @@ public async Task ContentLengthWithGetMemoryWorks() | |||
{ | |||
var response = httpContext.Response; | |||
response.ContentLength = 12; | |||
await response.StartAsync(); | |||
await Task.CompletedTask; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit
@@ -2681,7 +2727,6 @@ public async Task WriteAsync_GetMemoryLargeWriteBeforeFirstFlush() | |||
await InitializeConnectionAsync(async httpContext => | |||
{ | |||
var response = httpContext.Response; | |||
await response.StartAsync(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the test isn't explicitly about omitting StartAsync, and calling StartAsync is more efficient, call StartAsync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pending a few nits. I'd like a test verifying _isLeasedMemoryInvalid is reset for example.
Talked offline, there is actually a good amount of missed scenarios here, especially with resetting state in the Http1OutputProducer. I'll take a look at how to refactor this to avoid this issue in the future. |
@halter73 I added a reset method to HttpOutputProducer. All other ways weren't clean. |
For #7779.
One scenario this doesn't support is calling StartAsync after calling GetMemory and using the memory immediately after. @halter73 and I deemed it too difficult to fix well and is an incredibly niche scenario.
I added a check that would see if Advance was called immediately after calling StartAsync and throw if so.