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

When Streams are dropped, close Connection (#221) #222

Merged
merged 8 commits into from
Feb 15, 2018

Conversation

DarrenTsung
Copy link
Contributor

When all Streams are dropped / finished, the Connection was held
open until the peer hangs up. Instead, the Connection should hang up
once it knows that nothing more will be sent.

To fix this, we notify the Connection when a stream is no longer
referenced. On the Connection poll(), we check that there are no
active, held, reset streams or any references to the Streams
and transition to sending a GOAWAY if that is case.

These changes cause the example to work as expected (exiting when finished getting the response), but break other tests that are not expecting the connection to hang out.

Darren Tsung and others added 2 commits February 6, 2018 13:22
When all Streams are dropped / finished, the Connection was held
open until the peer hangs up. Instead, the Connection should hang up
once it knows that nothing more will be sent.

To fix this, we notify the Connection when a stream is no longer
referenced. On the Connection poll(), we check that there are no
active, held, reset streams or any references to the Streams
and transition to sending a GOAWAY if that is case.

These changes cause the example to work as expected (exiting when finished getting the response), but break other tests that are not expecting the connection to hang out.
@carllerche
Copy link
Collaborator

The PR looks good at a glance, but there is a complication.

Re the tests, I pushed a commit (7613406) that includes a fix for some of the tests. The issue with the client tests is that they drop the SendRequest handle early which causes the GO_AWAY frame to be sent. The fix is to hold on to the handle until the end of the test. Hopefully the commit I pushed illustrates that well enough.

Now, the complication.

It turns out that the server half uses the Connection to accept new streams, so it should be fine to hold on to the server connection without any outstanding refs.

I think what will need to happen is the logic to actually call transition_to_go_away is moved to server::Connection and client::Connection. On the client side, the same logic that you has applies. However, on the server side, the transition to go away will need to be an explicit call on Connection.

This relates to #69

@DarrenTsung
Copy link
Contributor Author

Thanks for the examples on how to fix the tests, I pushed a commit fixing the rest of the tests as well as splitting the logic between server / client such that client closes connection automatically and server has a close_connection() call that transitions the server to the GOAWAY state.

Let me know what you think!

@@ -204,6 +204,32 @@ fn sends_reset_cancel_when_req_body_is_dropped() {
srv.join(client).wait().expect("wait");
}

#[test]
fn sends_goaway_when_serv_closes_connection() {
Copy link
Member

Choose a reason for hiding this comment

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

I know you didn't originally intend to add graceful shutdown to the server, but it looks like this adds it! It'd be bonus points if there could be another test showing multiple requests being sent and accepted, and then close_connection() called before the responses (and their bodies?) are finished, that the outstanding streams can finish before the connection terminates.

(I suppose we'd also need to do something about any new streams sent after sending the goaway, I don't recall what the spec says to do. REFUSED_STREAM?)

Copy link
Contributor

Choose a reason for hiding this comment

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

The spec is slightly confusing on this matter. In one case, it says:

Receivers of a GOAWAY frame MUST NOT open additional streams on the connection

But it doesn't mention a protocol error. It seems to suggest those new streams can simply be discarded:

After sending a GOAWAY frame, the sender can discard frames for streams initiated by the receiver with identifiers higher than the identified last stream

With a caveat that data frames still need to count towards congestion flow control.

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 added a test for showing a request being accepted, then the server closing the connection on the next request (and subsequently ignoring the third request).

Not sure how to make sure any properties like congestion flow control still hold true, please let me know if any changes need to be made!

@DarrenTsung
Copy link
Contributor Author

Are there any other changes required?

@seanmonstar
Copy link
Member

Yikes, I forgot about this, sorry! I'll take a look again.

Copy link
Member

@seanmonstar seanmonstar left a comment

Choose a reason for hiding this comment

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

This is really exciting! I think there's just a couple small thoughts, which I left inline.

@@ -46,6 +46,10 @@ impl Counts {
self.peer
}

pub fn has_streams(&self) -> bool {
self.num_send_streams != 0 || self.num_recv_streams != 0 || self.num_reset_streams != 0
Copy link
Member

Choose a reason for hiding this comment

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

This probably doesn't need to check num_reset_streams, as that's just a count of "stream metadata we are holding on to temporarily to allow us to ignore incoming frames on a stream we just reset". The count is to just allow us to cap how many of those extra resources we keep in memory, but shouldn't affect if the connection is done, I believe.

// of canceling the stream), we should notify the task
// (connection) so that it can close properly
if stream.ref_count == 0 && stream.is_closed() {
if let Some(ref task) = actions.task.as_ref() {
Copy link
Member

Choose a reason for hiding this comment

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

This should probably be taken (actions.task.take()) as well. At least, that's the pattern that has been done through the lib whenever notifying on an Option<Task>.

@carllerche
Copy link
Collaborator

LGTM, I haven't noticed anything besides what @seanmonstar has called out.

@DarrenTsung
Copy link
Contributor Author

Updated with all your feedback, let me know!

@@ -46,6 +46,10 @@ impl Counts {
self.peer
}

pub fn has_streams(&self) -> bool {
self.num_recv_streams != 0 || self.num_reset_streams != 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it was num_reset_streams that was supposed to be ignored? num_send_streams is important. Is that correct @seanmonstar?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it is num_reset_streams that isn't needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh crap, my bad. Updated.

Copy link
Collaborator

@carllerche carllerche left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@carllerche carllerche merged commit 0c59957 into hyperium:master Feb 15, 2018
@carllerche
Copy link
Collaborator

CI passed 🎊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants