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

Make IHttpResponseStartFeature more forgiving #7779

Closed
halter73 opened this issue Feb 21, 2019 · 10 comments
Closed

Make IHttpResponseStartFeature more forgiving #7779

halter73 opened this issue Feb 21, 2019 · 10 comments
Assignees
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Done This issue has been fixed enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-pipelines Anything relating to exposing/using Pipes in ASP.NET Core

Comments

@halter73
Copy link
Member

Today, if you call ResponseBodyPipe.GetMemory() to start writing the response body without first calling IHttpResponseStartFeature.StartAsync(), GetMemory() will throw on Kestrel because the possibly-async response OnStarting callbacks haven't gotten the chance to run and we don't want to block in GetMemory() to wait for them.

Instead, we should make the behavior more forgiving by having GetMemory() return memory leased directly from the memory pool instead of the response body PipeWriter. This would continue until the first call to FlushAsync(). At that point, the OnStarting callbacks would be called, the headers would be committed, and the data written to the memory leased directly to the pool would be copied to the response body PipeWriter.

This way, calling IHttpResponseStartFeature.StartAsync() improves efficiency by avoiding copies before the first flush, but not calling StartAsync() doesn't break the app.

@davidfowl @jkotalik @Tratcher @muratg

@halter73 halter73 added enhancement This issue represents an ask for new feature or an enhancement to an existing one area-servers labels Feb 21, 2019
@JamesNK
Copy link
Member

JamesNK commented Feb 21, 2019

👍

This change also has the benefit of allowing a framework like gRPC to write a buffered message to BodyPipe, and still be able to modify headers afterwards (I don't think this is a common scenario but the API is there for someone to attempt to do it)

What is the ETA for this?

@JunTaoLuo
Copy link
Contributor

I believe this is going to be preview4.

@jkotalik
Copy link
Contributor

I don't know what discussions have occurred already, but we debated this when we were implementing StartAsync initially. I also prototyped this change too. There were a few problems.

  • Making this "Just work" will tank performance because all calls to GetMemory would require an extra copy and allocation until first write/flush.
  • It removes any reason for a user to call StartAsync as it isn't required. Making this throw is clear that what someone is doing is "wrong"
  • It reimplements a solid amount of pipe logic for renting/returning before first write. It wasn't pretty.

This change also has the benefit of allowing a framework like gRPC to write a buffered message to BodyPipe, and still be able to modify headers afterwards (I don't think this is a common scenario but the API is there for someone to attempt to do it)

I don't see why making this change would help that scenario. If you are replacing the PipeWriter to buffer the response, you are responsible for replacing the StartAsync feature too to make it so StartAsync doesn't call StartAsync on the original PipeWriter. See: https://github.com/aspnet/AspNetCore/blob/master/src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs#L57

@jkotalik jkotalik self-assigned this Feb 21, 2019
@jkotalik jkotalik added this to the 3.0.0-preview4 milestone Feb 21, 2019
@jkotalik
Copy link
Contributor

Putting this in preview4 no matter what because either we are doing it or we aren't

@JamesNK
Copy link
Member

JamesNK commented Feb 21, 2019

I don't see why making this change would help that scenario. If you are replacing the PipeWriter to buffer the response

We're not doing that today. We just write to the BodyPipe. I thought this change would mean were could write to BodyPipe and let it decide the best place to send the data to, depending on whether StartAsync is called or not.

I'm not sure how common the scenario I mentioned would be (a user writing gRPC messages with buffering and then modifying the headers afterwards). Without this change we probably wouldn't bother with doing extra work to support it, at least in the initial gRPC release.

@jkotalik
Copy link
Contributor

I thought this change would mean were could write to BodyPipe and let it decide the best place to send the data to, depending on whether StartAsync is called or not.

I see, what you would be doing would just work. Let's chat once I'm back in office.

@JamesNK
Copy link
Member

JamesNK commented Feb 21, 2019

One extra thing to add: there is currently a difference in behavior between writing to Response.BodyPipe in an actual Kestrel server vs TestServer.

Writing to BodyPipe without StartAsync in Kestrel server: exception
Writing to BodyPipe without StartAsync in TestServer: no exception

I'm guessing this is because TestServer is using StreamPipeWriter that doesn't require StartAsync to be called.

TestServer differences with the real world are concerning because we have a lot of tests are functional tests using TestServer.

@Tratcher
Copy link
Member

Right, TestServer has an internal response pipe but it hasn't been exposed publicly and you still end up using the wrappers. Getting TestServer updated is almost as important as Kestrel, but I don't think there's an issue tracking that.

@Tratcher
Copy link
Member

Actually, this brings up a different issue, why does StreamPipeWriter behave differently from Kestrel? Maybe it should require StartAsync. That's not TestServer's problem, it's StreamPipeWriter's.

@davidfowl
Copy link
Member

No, we're just going to change Kestrel. It'll buffer by default and will be less efficient unless you call StartAsync. We'll do it in preview4.

@jkotalik jkotalik added the feature-pipelines Anything relating to exposing/using Pipes in ASP.NET Core label Feb 21, 2019
@jkotalik jkotalik added Done This issue has been fixed and removed 2 - Working labels Mar 8, 2019
@jkotalik jkotalik closed this as completed Mar 8, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Done This issue has been fixed enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-pipelines Anything relating to exposing/using Pipes in ASP.NET Core
Projects
None yet
Development

No branches or pull requests

8 participants