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

Avoid sending EndStream after RST_STREAM with dedicated lock #54552

Merged

Conversation

alnikola
Copy link
Contributor

A race condition between sending RST_STREAM and checking conditions for sending EndStream was discovered during stress testing. It happens to be possible that in time after Http2Stream checked the _responseCompletionState and actually send EndStream, a concurrent call to Cancel method sends a RST_STREAM frame. Such reordering is disallowed by HTTP/2 protocol.

Note: The issue and fix were verified manually under the debugger because currently it's not clear how to reliably simulate that situation.

Fixes #42200

@ghost
Copy link

ghost commented Jun 22, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

A race condition between sending RST_STREAM and checking conditions for sending EndStream was discovered during stress testing. It happens to be possible that in time after Http2Stream checked the _responseCompletionState and actually send EndStream, a concurrent call to Cancel method sends a RST_STREAM frame. Such reordering is disallowed by HTTP/2 protocol.

Note: The issue and fix were verified manually under the debugger because currently it's not clear how to reliably simulate that situation.

Fixes #42200

Author: alnikola
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

@alnikola alnikola requested a review from a team June 22, 2021 15:06
Copy link
Contributor

@geoffkizer geoffkizer left a comment

Choose a reason for hiding this comment

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

In general, I think we should ensure any calls to SendEndStreamAsync or SendResetStreamAsync are under the SyncObj lock. This should ensure there are no weird races between sending these.

@ViktorHofer ViktorHofer reopened this Jun 22, 2021
@alnikola alnikola added this to the 6.0.0 milestone Jun 22, 2021
@@ -384,9 +387,13 @@ private void Cancel()
// When cancellation propagates, SendRequestBodyAsync will set _requestCompletionState to Failed
requestBodyCancellationSource?.Cancel();

if (sendReset)
// SendReset must be called under a lock and after a cancellation of requestBodyCancellationSource.
lock (SyncObject)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not really happy to lock on the same object just after leaving the above lock block, but it seems dangerous to change order of requestBodyCancellationSource?.Cancel() and SendReset calls as well as to move requestBodyCancellationSource?.Cancel() under the above lock.

@alnikola
Copy link
Contributor Author

@geoffkizer I addressed your comments, please review it again.

@@ -384,9 +387,13 @@ private void Cancel()
// When cancellation propagates, SendRequestBodyAsync will set _requestCompletionState to Failed
requestBodyCancellationSource?.Cancel();

if (sendReset)
// SendReset must be called under a lock and after a cancellation of requestBodyCancellationSource.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it actually have to happen after we cancel requestBodyCancellationSource? It seems like the cancellation is going to propagate asynchronously anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like the cancellation is going to propagate asynchronously anyway.

That was my main concern about reordering. I'm not sure if it always propagates asynchronously. Need take a closer look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems cancellation callbacks can be invoked on the canceling thread when there is no a synchronization context set, so I believe we shouldn't reorder calls here.

Copy link
Contributor

Choose a reason for hiding this comment

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

The cancellation callback will be called on the canceling thread. But that doesn't guarantee anything about when cancellation propagates.

If you don't think we should change the ordering here, that's fine with me; I agree it's safer not to. But let's change or remove the comment because it makes it sound like the ordering is strictly necessary, when I don't think we know that for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. I removed the comment.

Copy link
Contributor

@geoffkizer geoffkizer left a comment

Choose a reason for hiding this comment

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

In general looks good -- one question about requestBodyCancellationSource above.

@alnikola alnikola merged commit 5d29560 into dotnet:main Jun 29, 2021
@alnikola alnikola deleted the alnikola/42200-dont-send-eof-after-rst-stream branch June 29, 2021 18:00
@ghost ghost locked as resolved and limited conversation to collaborators Jul 29, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HTTP/2 stress server intermitently fails in duplex scenarios
3 participants