Skip to content

http: add onComplete to AsyncClient::StreamCallbacks#7752

Merged
mattklein123 merged 16 commits intoenvoyproxy:masterfrom
goaway:ms/on-closure
Aug 7, 2019
Merged

http: add onComplete to AsyncClient::StreamCallbacks#7752
mattklein123 merged 16 commits intoenvoyproxy:masterfrom
goaway:ms/on-closure

Conversation

@goaway
Copy link
Contributor

@goaway goaway commented Jul 29, 2019

Description: Add onComplete callback for asymmetric cases where end_stream may not be bidirectionally observable.
Risk Level: Medium
Testing: Updated unit tests

Co-authored-by: Jose Ulises Nino Rivera jnino@lyft.com
Signed-off-by: Mike Schore mike.schore@gmail.com

goaway added 2 commits July 29, 2019 14:14
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
@goaway
Copy link
Contributor Author

goaway commented Jul 29, 2019

To expand on this, receiving end_stream on a callback does not necessarily indicate the stream is closed; since locally the stream may still be open. While this is something of a special case, it's enough to warrant the bookkeeping we see here and elsewhere with local and remote flags.

If AsyncClient and friends are used from asymmetric code paths (sending stuff is somewhere separate from receiving stuff), it's not currently possible to infer when a stream is actually closed. Even in the case of bidirectional usage, since local and remote closure states are private, this bookkeeping must otherwise be replicated by anything that ties resources to stream lifecycle.

Signed-off-by: Mike Schore <mike.schore@gmail.com>
@goaway
Copy link
Contributor Author

goaway commented Jul 29, 2019

In the original PR I stated, "This isn't required for AsyncRequest, since a request is a complete entity and the local stream will be closed as soon as it is sent." This is technically incorrect, since a remote could in theory return a response with end_stream set prior to the request completing. This seems like much more of an edge case, but I've updated it in this PR as well.*

goaway added 2 commits July 29, 2019 14:48
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
goaway added 2 commits July 29, 2019 15:13
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
@mattklein123 mattklein123 self-assigned this Jul 29, 2019
rebello95
rebello95 previously approved these changes Jul 29, 2019
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks this makes sense to me. Please check CI.

/wait

goaway added 2 commits July 29, 2019 16:55
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
@goaway
Copy link
Contributor Author

goaway commented Jul 30, 2019

Thanks for the feedback so far everyone. Will make updates. (Also still stepping through one failing test.)

@goaway goaway changed the title http: add onClosure to AsyncClient::StreamCallbacks http: add onComplete to AsyncClient::StreamCallbacks Jul 30, 2019
Signed-off-by: Jose Nino <jnino@lyft.com>
Signed-off-by: Jose Nino <jnino@lyft.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
@goaway
Copy link
Contributor Author

goaway commented Jul 31, 2019

(ready for review)

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. This seems like a nice improvement. Some questions.

/wait-any


void AsyncStreamImpl::closeLocal(bool end_stream) {
ASSERT(!(local_closed_ && end_stream));
// Due to the fact that send calls can synchronously result in stream closure, it's possible for
Copy link
Member

Choose a reason for hiding this comment

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

I don't think I follow how this is happening. What's the callstack when we get called after being already closed?

Copy link
Contributor Author

@goaway goaway Aug 1, 2019

Choose a reason for hiding this comment

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

I'm inferring from tests, but I believe this can happen when the connection pool fails to provide a connection, and surfaces this via a locally-created 503 response.

Copy link
Member

Choose a reason for hiding this comment

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

But how does that cause closeLocal() to get called twice?


void AsyncStreamImpl::closeRemote(bool end_stream) {
remote_closed_ |= end_stream;
// Due to the fact that callbacks can synchronously result in stream closure, it's possible for
Copy link
Member

Choose a reason for hiding this comment

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

Again a bit hazy on how this happens. Can you provide more info?

Copy link
Contributor Author

@goaway goaway Aug 1, 2019

Choose a reason for hiding this comment

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

In this case, it's possible when a callback synchronously calls stream->reset. There's also a unit test that covers this.

Copy link
Member

Choose a reason for hiding this comment

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

But if closes only happen in non-reset cases, why are we calling closeRemote() in the case of reset?

/wait-any

Copy link
Contributor Author

@goaway goaway Aug 2, 2019

Choose a reason for hiding this comment

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

Let's assume the case covered by the unit test.
We're in encodeHeaders:

void AsyncStreamImpl::encodeHeaders(HeaderMapPtr&& headers, bool end_stream) {

stream_callbacks.onHeaders is called and synchronously resets the stream. reset calls resetStream which calls cleanup:

cleanup sets local and remote to be closed:

local_closed_ = remote_closed_ = true;

the callback finishes executing, but we're still in encodeHeaders, which looks to see if end_stream is true, and if it is, goes ahead and calls closeRemote:

closeRemote(end_stream);

This is largely existing behavior, which this PR preserves, except for the added guards to prevent "extra" closures/resets.

Copy link
Member

Choose a reason for hiding this comment

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

OK I see. That was my confusion that we are using local_closed_ and remote_closed_ also in the case of reset. IMO this is kind of confusing, so if you want to somehow split this logic out I would go for that. If not, can you potentially make the comments in these cases a lot more robust for the next reader to come along? I think that would be good enough for me. Thank's for explaining all of this.

/wait

Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks for the additional comments, this makes sense to me. I will wait a little bit to merge in case @htuch or @lizan have any additional comments. Thank you!

}

void AsyncStreamImpl::closeLocal(bool end_stream) {
// TODO(goaway): This assert maybe merits reconsideration. It seems to be saying that we shouldn't
Copy link
Member

Choose a reason for hiding this comment

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

Yeah I agree, I don't remember the history here. We should look at cleaning this up. cc @htuch @lizan in case they remember any of this history.

@mattklein123 mattklein123 merged commit 67103ba into envoyproxy:master Aug 7, 2019
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.

5 participants