-
Notifications
You must be signed in to change notification settings - Fork 10.4k
ResponseCaching: started conversion to pipes #16961
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
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.
Didn't look at the overall design, just these nits.
src/Middleware/ResponseCaching/src/CacheEntry/CachedResponseBody.cs
Outdated
Show resolved
Hide resolved
src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs
Outdated
Show resolved
Hide resolved
I had another look at this and it looks like the pipe writer is built around the stream, so keep wrapping the original stream doesn't give a performance disadvantage. |
@jkotalik can you take a look at this and see what you think of the design so we know how to move forward? |
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.
It also remove a memory copy when saving to the cache, however I do not understand for which scenario it was needed before (and so it would be still needed now) or if it was just a consequence of the design.
From what I can tell, what you did looks like strictly an improvement.
Migrating the capturing of the response to cache, getting read of the stream implementation, is more tricky and I am not sure what it would be a correct design.
Currently it rely on replacing the Body with a shim of the response stream that capture all the writes and duplicate them to buffer, however it is not possible to replace the BodyWriter instead.
It definitely is possible to replace the BodyWriter instead, however I can see that there would be some difficulties. From what I can tell, when someone calls WriteAsync, the stream shim calls FinalizeCacheHeaders, which sets some headers on the response (see https://github.com/aspnet/AspNetCore/blob/e659b55c33b20a92737a43427b60c5c73375ae5b/src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs#L254). The difficulties with the BodyWriter will be that when someone calls GetMemory/GetSpan/Advance to write part of the response body, then call FlushAsync(), the headers need to be written to the response before writing the response body itself. If someone wrote content to GetMemory, we would somehow need to inject the headers before. The only way to do this is saving all writes before first flush and then make the pipe passthrough afterwards.
We solved this in kestrel itself on the original PipeWriter by doing internal tracking/leasing: https://github.com/aspnet/AspNetCore/blob/master/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs#L207. This logic is fairly straight forward; you should be able to use a lot of what exists there.
I don't know it's worth shimming the response stream with a pipe here though, the added complexity it introduces will make it hard to maintain and I don't know how much of a perf benefit you'd get for it.
I think the current change looks like something we would take though.
src/Middleware/ResponseCaching/src/CacheEntry/CachedResponseBody.cs
Outdated
Show resolved
Hide resolved
src/Middleware/ResponseCaching/src/CacheEntry/CachedResponseBody.cs
Outdated
Show resolved
Hide resolved
|
||
public override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
if (buffer == null) |
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.
Was read ever used? Otherwise, makes sense.
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.
SegmentReadStream
was used to wrap the data stored in the cache, so Read
was using when serving from cache.
Now I'm using CachedResponseBody just as a wrapper of the byte segments, which then copy to the pipewriter without using a stream, so this is no longer needed.
Is that what you were asking?
e659b55
to
6e7c63a
Compare
Hi @jkotalik , About this PR, I've rebased it and addressed the feedback. |
I've also added a benchmark and it seems like slightly slower then before although I'm not sure the benchmark is really representative and the error margin is quite high, Before:
After:
|
Hmmmmmmmm... that's not ideal. Given that we're already a little unsure of the value of this change I'm not sure it's worth it right now. |
I've reworked the benchmark, using an actual It would be great if someone can review the benchmark to confirm the validity of these results: Before:
After:
|
Next steps: @JunTaoLuo 's going to clone and take a look and do some checking here, as well as checking the benchmark validity. We'll also need to rebase ;). @alefranz can you do that? The benchmark results look OK to me, so it seems like it's worth taking if only so that we use more pipes. |
2c62d94
to
17ae79c
Compare
HI @anurse , @JunTaoLuo , I've rebased the PR. Thank you! |
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.
Changes look good. I'm a fan of the simplification and the benefit with large responses. I agree that we don't need to worry about the response stream for now so this will be ready to merge once rebased.
src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs
Outdated
Show resolved
Hide resolved
2dfb367
to
21e4b3e
Compare
Addressed feedback and rebased. Thank you! |
Looks like there's a test failure:
Given that this is failing in one out of many runs, it's likely that it's flaky. |
@alefranz can you look into fixing the flaky test? |
Hi John, apologies for the delay. |
Thanks for the contribution! |
Thank you for your help! |
I've started to look into migrating the
ResponseCaching
middleware to pipes.For now this only use pipes to write the response from the cache.
This already allow to remove some code.
It also remove a memory copy when saving to the cache, however I do not understand for which scenario it was needed before (and so it would be still needed now) or if it was just a consequence of the design.
Migrating the capturing of the response to cache, getting read of the stream implementation, is more tricky and I am not sure what it would be a correct design.
Currently it rely on replacing the
Body
with a shim of the response stream that capture all the writes and duplicate them to buffer, however it is not possible to replace theBodyWriter
instead.Also, should not the shim be applied on the
IHttpResponseBodyFeature
, like it is done in theResponseCompression
middleware?I imagine would make sense to have a common design for both middlewares.
I will keep looking at this, but I opened this draft PR so if you already have a design in mind we can discuss it.
I don't think merging only this part makes sense.
Related #11377