-
Notifications
You must be signed in to change notification settings - Fork 10.4k
BodyWriter polyfill doesn't flush, causes truncation #11305
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
Comments
No way, that's extremely disruptive and shouldn't have to be a middleware, one thing we need to do to a work around problems like this (having a middleware) is to add a new event to the pipeline (IHttpResponse.OnCompleting) that fires before everything is torn down so that we can perform flushes like this without requiring a middleware as part of the pipeline. It's so much cleaner and is localized to the callsite and requires less moving parts. This event would make it possible to implement the buffering middleware without middleware 😄 PS: Not calling FlushAsync on the Pipe is bad usage anyways so I don't think this is critical, not calling Complete is a much worse behavior. It's likely in the future that |
OnCompleting would be an ordering mess vs middleware already in the pipeline. It only works if everybody uses it, and in order. E.g. You have the response compression middleware that replaces the response Body. The application writes to the BodyPipe via the polyfill. Then the response compression middleware completes and closes out the compression state. Then OnCompleting fires and the polfyfill tries to flush the BodyPipe. Disaster. Actually, the above scenario isn't much better with the polyfill middleware, you would have to add the middleware after response compression in order for it to work. |
Note the long term solution here is the merger of pipes and streams so the polyfill is built in at a lower level. |
I don't agree. Having to add a middleware to the pipeline to use a property on HttpContext is a mistake (it's why I hate having session on the HttpContext!). We would make everyone use the event, I don't see what the problem is with that, it's just like OnStarting, an established pattern in our stack (everyone has to use it an chain it to make "the right thing happen"). I don't see why we don't have an equivalent on the response side. It's a gap.
How does that help? |
OnStarting has been such a pain that we abandon most uses of it. It doesn't shim, chain, or fail well. Instead, we end up intercepting the response body in almost all cases.
Would this adapter be implemented any differently if it were a polyfill built into the Stream class? Maybe it would auto-flush, maybe not. But it would lower the bar for implementing BodyWriter in each server, e.g. if all they had to do was call flush on their own stream at the end of a request, kind of like what James tried in his TestServer PR. |
It's the pain we have today and because we don't buffering everything by default, we'll always need something like this. We need an event that fires at the end of the pipeline so things like this can be implemented without middleware.
It would not help, Streams have the exact same issue, anything that buffers has the exact same issue, it's the problem of the call site not calling DisposeAsync/Complete/FluhsAsync on anything that buffers, and expecting something else in the pipeline to do it. Merging pipes and streams solves nothing and we need to either put this behavior into every server or empower other pieces of the stack the way we do with OnStarting (i.e. OnCompleting) to achieve similar behavior. That's the solution I'd prefer for something like this. The middleware approach is a non-starter IMO. |
|
Requring |
If you really want to require FlushAsync, perhaps CompleteAsync could be the way to send data and trailers together? |
I discussed this a bit with @Tratcher and he brought up a few issues. I like this idea, but if we do it, any middleware that replaces the response pipe would also need to replace CompleteAsync for that to work. Middleware that just replaces the body stream would be broken too because the pipe get replaced implicitly, but they don't replace CompleteAsync. |
Is there something here that would be ship-blocking for 3.0 or can we look at it in 3.1? |
This will get fixed when we add CompleteAsync to PipeWriter. Assign this to me as the reaction |
How will PipeWriter.CompleteAsync fix this? Are you assuming the app will call it? |
Complete now flushes buffered data and CompleteAsync will do the same but asynchronously |
That doesn't address the issue I originally posted above, Complete is still getting called too late (during response disposal). The adapter only works if you require the app to explicitly call Flush or Complete. |
Ah, then we need a new event in the pipeline where we can complete/flush without blowing up because it's too late (and no, middleware isn't the way to solve this). |
I agree middleware isn't going to work, the pipe can be adapted multiple times in arbitrary places. Adding an event for this is going to introduce more ordering and state issues. E.g. events would have to fire in reverse registration order (stack) to mimic pipeline order, and each middleware would have to track if its event was fired before the pipeline started to unwind and avoid double work. We abandoned the OnStarting event for similar reasons. Maybe blowing up and forcing the the app to call Flush or Complete is the best answer here. |
I'm going to set a meeting and we're going to come up with what we're doing (if anything) for 3.0. |
Issues:
Examples:
Kestrel GetMemory & Advance W/Response Compression
HttpSys GetMemory & Advance
Kestrel GetMemory, Advance, & FlushAsync W/Response Compression
HttpSys GetMemory, Advance, & FlushAsync
Kestrel GetMemory, Advance, & Complete W/Response Compression
HttpSys GetMemory, Advance, & Complete
Kestrel GetMemory, Advance, & CompleteAsync W/Response Compression
HttpSys GetMemory, Advance, & CompleteAsync
Kestrel GetMemory, Advance, & Feature.CompleteAsync W/Response Compression (No response body received)
Kestrel GetMemory, Advance, FlushAsync, & Feature.CompleteAsync W/Response Compression
|
Proposals: HttpRequest/Response.Body Set adapts New mondo features: IHttpRequestBodyFeature IHttpResponseBodyFeature Obsolete Delete |
What about IHttpBodyControlFeature.AllowSynchronousIO? |
I think this feature needs to be an abstract base class the moment we need to add optional features like that. Do you have a shape in mind yet? Can you write a class proposal? |
I'm experimenting locally. An abstract base class would make it really hard for servers to implement, they all have custom feature collections. |
True, it means we're going to lean on default interface methods. |
@Tratcher to close once the breaking changes are announced. |
#11268 identified a truncation issue with the HttpContext.Response.BodyWriter polyfill logic.
The BodyWriter polyfill automatically creates a pipe wrapped around the stream. If you use this pipe to call GetMemory and Advance but do not call FlushAsync, this data is not flushed to the underlying stream. When the request unwinds the pipe will eventually be completed by the OnCompleted event, but it's too late to flush the data as the response has already been disposed.
This is different behavior from any server that does expose pipes (Kestrel) where any data in the pipe is automatically flushed by the server when it completes the pipe.
Proposal: This polyfill should have been implemented as middleware that could auto-flush the pipe at the end of the request.
Note that polyfill middleware would also need to intercept CompleteAsync and flush in that case as well.
Counter proposal: The app is always required to call FlushAsync.
@davidfowl @JamesNK
The text was updated successfully, but these errors were encountered: