Skip to content

[http] Introduce error status return type for dispatch (no-op)#10879

Merged
mattklein123 merged 18 commits intoenvoyproxy:masterfrom
asraa:common-codec-change
May 5, 2020
Merged

[http] Introduce error status return type for dispatch (no-op)#10879
mattklein123 merged 18 commits intoenvoyproxy:masterfrom
asraa:common-codec-change

Conversation

@asraa
Copy link
Copy Markdown
Contributor

@asraa asraa commented Apr 21, 2020

(This is the merge-able portion of #10484. It does not include the behavior changes in H/1. Only the necessary changes are there for H/1 and H/2 exception removal to happen separately.)

Description: This introduces a new return value, Envoy::Http::Status for Connection::dispatch. Currently, dispatch will always return an OK status. This facilitates the migration to remove exceptions in codecs and replace them with error statuses. The HCM and codec client can now handle statuses returned from dispatch, although they will continue to support exceptions (by translating them to the corresponding status) until legacy codecs are deprecated.

Following this, PRs will stage exception removal in H/1 and H/2 codecs, codecs will be forked to have a legacy version, and a runtime override will allow users to continue to use legacy codecs during the migration.

See issue #10878 for a full detailed plan and overview.

Risk: Medium-high. A no-op, but touches sensitive code.
Testing: All tests pass, this is a no-op
Issue: #10878

@yanavlasov

asraa added 2 commits April 21, 2020 10:48
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
* callbacks.
*/
virtual void dispatch(Buffer::Instance& data) PURE;
virtual Envoy::Http::Status dispatch(Buffer::Instance& data) PURE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You do not need to fully qualify Status namespace here. I think just Status should work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done -- I changed the rest of the namespaces as well, opting for Http::Status, since Http::Connection is also used

* Dispatch incoming connection data.
* @param data supplies the data to dispatch. The codec will drain as many bytes as it processes.
* @return Envoy::Http::Status indicating the status of the codec. Holds any errors found in the
* callbacks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe something like this: "Holds any errors encountered during processing of incoming data" ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

asraa added 4 commits April 22, 2020 12:21
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
asraa added 3 commits April 23, 2020 09:17
Signed-off-by: Asra Ali <asraa@google.com>
@asraa
Copy link
Copy Markdown
Contributor Author

asraa commented Apr 27, 2020

ping?

@asraa
Copy link
Copy Markdown
Contributor Author

asraa commented Apr 29, 2020

@mattklein123 Hey Matt! Could you please take a look at this? It is code pulled from #10484 that doesn't include the local H/1 changes, but just no-op return value change that would handle (currently all OK) error statuses from codecs.

@jmarantz jmarantz self-assigned this Apr 29, 2020
@mattklein123 mattklein123 self-assigned this Apr 29, 2020
@mattklein123
Copy link
Copy Markdown
Member

Will take a look, but can @yanavlasov please review first and approve?

Copy link
Copy Markdown
Contributor

@jmarantz jmarantz left a comment

Choose a reason for hiding this comment

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

I haven't gone over the tests in detail yet but here's a first pass.

Comment on lines +328 to +337
try {
codec_->dispatch(data);
status = codec_->dispatch(data);
// TODO(#10878): Remove this when exception removal is complete. It is currently in migration.
// Soon we won't need to catch these exceptions, as they'll be propagated through the error
// statuses callbacks and returned from dispatch.
} catch (const FrameFloodException& e) {
handleCodecException(e.what());
return Network::FilterStatus::StopIteration;
status = bufferFloodError(e.what());
} catch (const CodecProtocolException& e) {
status = codecProtocolError(e.what());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we factor out this stanza from codec_client.c? It looks identical.

The stanza below is different of course but this looks like it would make a good helper function callable in both places.

Copy link
Copy Markdown
Contributor Author

@asraa asraa Apr 29, 2020

Choose a reason for hiding this comment

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

qq before I make this change (as it's pretty big) about where to factor this out to:

  1. utility function that takes codec and data. pros: will be simple to remove when exception removal.
  2. I could factor this out so it's baked into ConnectionImpl::dispatch, i.e. dispatch will always return a status now (by calling a helper with the try/catch block around an innerDispatch and then doing the exception to status conversion). This has the advantage of minimizing the diff between the new and legacy codec_impl_tests. calls to dispatch will always return a status, not a status sometimes and a throw for legacy. I am leaning to this option.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah +1 for (2) that sounds great.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done, I wanted to at least try that suggestion out. A uniform return value from dispatch is clean and causes less confusion.

There was one big pain point, see the diff in http2/codec_impl_test.cc. This test

EXPECT_THROW_WITH_MESSAGE(response_encoder_->encodeHeaders(early_hint_headers, false),
will fail unless I directly call the innerDispatch in the mock connection in the test fixture. Why? Because encodeHeaders throws, and if I directly call dispatch, I swallow the throw because write(_,_) does not return a status. The stack trace for the failure when that happens goes through encodeHeaders, encodeHeadersBase, sendPendingFrames(), and then throws. This throw does not occur in a dispatching context! see
if (dispatching_ || connection_.state() == Network::Connection::State::Closed) {

Hence, this solution is kinda ugly because in H/2 test I had to call in inner dispatch, because we test a non-dispatching throw.

ENVOY_CONN_LOG(trace, "dispatching {} bytes", connection_, data.length());
// Make sure that dispatching_ is set to false after dispatching, even when
// ConnectionImpl::dispatch throws an exception.
Cleanup cleanup([this]() { dispatching_ = false; });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TODO to make this not throw and thus remove this hack? It seems unexpected to have a method that returns a status and can also throw.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you are still going to need this given early returns?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Even with early returns, we would still be executing a dispatching_ = false line at the end of the method if we are guaranteed not to throw, right? Or early returns like possible RELEASE_ASSERTs? If so I'll add a clarification

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I just meant early returns in this function, unless you structure it in such a way to only have a single return at the end. It's not a big deal either way, we can sort it in future PRs?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this function still call others that throw? Or are the throws only self-inflicted in the loop below? Maybe we can just clean this up right here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gotcha -- yeah, it'll depend on the next PR. I clarified, left the issue # in there just for remembering in future PRs when it comes up!

Copy link
Copy Markdown
Contributor Author

@asraa asraa May 1, 2020

Choose a reason for hiding this comment

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

Does this function still call others that throw?

Yeah -- nghttp2_session_mem_recv may throw in a callback, so I don't think it's possible to clean up right now.

Comment on lines +449 to +457
Http::Status status;
try {
parent_.codec_->dispatch(data);
status = parent_.codec_->dispatch(data);
// TODO(#10878): Remove this when exception removal is complete. It is currently in
// migration. Soon we won't need to catch these exceptions, as they'll be propagated through
// the error statuses callbacks and returned from dispatch.
} catch (const Http::CodecProtocolException& e) {
ENVOY_LOG(debug, "FakeUpstream dispatch error: {}", e.what());
status = Http::codecProtocolError(e.what());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks similar to the factorizable stanza above, though there's one less 'catch' in it. Re-use the suggested factorered helper-method out here anyway?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
while (buffer_.length() > 0) {
dispatching_ = true;
connection.dispatch(buffer_);
status = connection.innerDispatch(buffer_);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I felt bad about writing this line. FYI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you maybe add a comment/TODO to reference exception removal for someone that comes across this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
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.

You definitely win the epic slog/refactor award. 👏 :)

re: the exception that you had to work around with calling innerDispatch, this is another example of a case in which that just shouldn't ever happen since no one should ever call it that way (similar to H1 trying to make a client call without host, method, etc.). I'm not sure how we want to handle this, but I would probably advocate just moving those cases to RELEASE_ASSERT and making them death tests, but I don't feel strongly about it. If you want to do that in a follow up I would just put in relevant TODO/comment as I mentioned in the review. Thank you!

/wait

codec_->dispatch(data);
} catch (CodecProtocolException& e) {
ENVOY_CONN_LOG(debug, "protocol error: {}", *connection_, e.what());
Status status = codec_->dispatch(data);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: const, same in other places if applicable.

ENVOY_CONN_LOG(trace, "dispatching {} bytes", connection_, data.length());
// Make sure that dispatching_ is set to false after dispatching, even when
// ConnectionImpl::dispatch throws an exception.
Cleanup cleanup([this]() { dispatching_ = false; });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you are still going to need this given early returns?

while (buffer_.length() > 0) {
dispatching_ = true;
connection.dispatch(buffer_);
status = connection.innerDispatch(buffer_);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you maybe add a comment/TODO to reference exception removal for someone that comes across this?

Signed-off-by: Asra Ali <asraa@google.com>
ENVOY_CONN_LOG(trace, "dispatching {} bytes", connection_, data.length());
// Make sure that dispatching_ is set to false after dispatching, even when
// ConnectionImpl::dispatch throws an exception.
Cleanup cleanup([this]() { dispatching_ = false; });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this function still call others that throw? Or are the throws only self-inflicted in the loop below? Maybe we can just clean this up right here?

} catch (CodecProtocolException& e) {
status = codecProtocolError(e.what());
} catch (PrematureResponseException& e) {
status = prematureResponseError(e.what(), e.responseCode());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we also catch EnvoyException to make sure there are no others that leak through?

or maybe even std::exception?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would rather not catch any more exceptions than we do today and otherwise let the process crash? I'm not sure why we would expand the catch targets as part of this cleanup?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sg. if we don't have a higher level catch then expanding the scope here isn't needed.

Signed-off-by: Asra Ali <asraa@google.com>
@mattklein123
Copy link
Copy Markdown
Member

LGTM but might need a tidy fix. Nice work!

/wait

asraa added 2 commits May 4, 2020 12:04
Signed-off-by: Asra Ali <asraa@google.com>
@mattklein123
Copy link
Copy Markdown
Member

/azp run envoy-presubmit

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

mattklein123
mattklein123 previously approved these changes May 4, 2020
Copy link
Copy Markdown
Contributor

@yanavlasov yanavlasov left a comment

Choose a reason for hiding this comment

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

LGTM modulo minor nit. Thanks for getting this done.

}

void ConnectionManagerImpl::handleCodecException(const char* error) {
void ConnectionManagerImpl::handleCodecException(absl::string_view error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: make it handleCodecError ? Since it is not an exception anymore.

Signed-off-by: Asra Ali <asraa@google.com>
@mattklein123 mattklein123 merged commit 23185de into envoyproxy:master May 5, 2020
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