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

Always wait for the request content to complete #1271

Merged
merged 7 commits into from
Oct 11, 2021

Conversation

MihaZupan
Copy link
Member

Fixes #1100

Ensure that we always wait for the request content copy to complete before returning from SendAsync.

When using HTTP/2, the request content copy is done in parallel. It may still be going on even if SendAsync throws.
In such cases, the cancellation token passed to SerializeToStreamAsync is signaled. This PR makes sure we combine the tokens appropriately.

On 3.1, there is no overload of SerializeToStreamAsync that takes a CT, so we create an extra CTS that we cancel manually in case of an exception.

Perf:
On 3.1 we can't detect whether HTTP/1.1 is being used, so we always allocate an extra CTS.
On 5.0+, we allocate an extra CTS only when using HTTP/2 (it would be trivial to pool it).

src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/HttpForwarder.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/HttpForwarder.cs Outdated Show resolved Hide resolved
src/ReverseProxy/Forwarder/HttpForwarder.cs Show resolved Hide resolved
Comment on lines 168 to 174
if (requestContent is not null && requestContent.Started)
{
await requestContent.ConsumptionTask;
}

// The transforms callback decided that the response body should be discarded.
destinationResponse.Dispose();
Copy link
Member

Choose a reason for hiding this comment

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

This scenario is really interesting. The caller has decided to discard the response body but it's unclear what the intent is for the request. In a worst case bidirectional streaming scenario the request content might never finish. An echo server would hang since the response isn't being consumed or disposed yet.

At a minimum, dispose the response before waiting for the request so the server knows you're not going to consume it. I think you still need to cancel the request content since it might be waiting for client input.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like HttpClient will cancel the request content if you dispose the response without reading it.

The server responded in a way that the proxy doesn't want the client to see. Presumably, the server doesn't care about the request either (in which case we should cancel it)? The alternative of potentially waiting for a long time is arguably worse as you mention.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. The one thing to watch out for is if canceling the request body causes the client to error out and not see the proxy response at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

if canceling the request body causes the client to error out

As in Kestrel having side effects if a read to its original request body is canceled?

Copy link
Member

Choose a reason for hiding this comment

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

Right. That will depend a lot on the request and the timing, so it's something we'll need to watch out for in the future to see if it becomes a problem.

Naming, consistency
@Tratcher Tratcher assigned MihaZupan and unassigned Tratcher Oct 7, 2021
Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

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

Any insights on the HTTP/3 question?

src/ReverseProxy/Forwarder/StreamCopyHttpContent.cs Outdated Show resolved Hide resolved
@samsp-msft samsp-msft added the samsp_list Personal tag used when reviewing issues for further discussion label Oct 8, 2021
@MihaZupan
Copy link
Member Author

MihaZupan commented Oct 11, 2021

There are two main cases where we try to forcefully cancel the request content:

  • SendAsync threw an exception
  • The user chose not to copy the response body

If SendAsync throws:

  • HTTP/1: the content either hasn't started or it was the cause of the exception - no need to do anything
  • HTTP/2: the CT passed to the content will be signaled and we can react
  • HTTP/2 before .NET 5.0: We have a manual CTS we cancel to signal the CT

The interesting case is if the user chooses not to copy the body.
Before 5.0: we cancel the manual CTS
5.0+: we just wait for the request task to complete, which won't happen until a write is attempted.

It appears we may need a CTS we can cancel manually in all cases, not just 3.1.
We should be able to use the same CTS (requestTimeoutSource) for all of this.

@Tratcher
Copy link
Member

It appears we may need a CTS we can cancel manually in all cases, not just 3.1.
We should be able to use the same CTS (requestTimeoutSource) for all of this.

Yeah, I think we were going to end up in that position regardless when I did my ActivityTimeout work.

@MihaZupan
Copy link
Member Author

Changed the logic to use the request CTS for manual cancellations.

Added a manual cancel to HandleResponseBodyErrorAsync before waiting on the request to complete.

@MihaZupan MihaZupan merged commit 42b8502 into microsoft:main Oct 11, 2021
This was referenced Oct 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
samsp_list Personal tag used when reviewing issues for further discussion
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make sure HttpForwarder waits for the request body
3 participants