[http] Introduce error status return type for dispatch (no-op)#10879
[http] Introduce error status return type for dispatch (no-op)#10879mattklein123 merged 18 commits intoenvoyproxy:masterfrom
Conversation
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
include/envoy/http/codec.h
Outdated
| * callbacks. | ||
| */ | ||
| virtual void dispatch(Buffer::Instance& data) PURE; | ||
| virtual Envoy::Http::Status dispatch(Buffer::Instance& data) PURE; |
There was a problem hiding this comment.
You do not need to fully qualify Status namespace here. I think just Status should work.
There was a problem hiding this comment.
Done -- I changed the rest of the namespaces as well, opting for Http::Status, since Http::Connection is also used
include/envoy/http/codec.h
Outdated
| * 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. |
There was a problem hiding this comment.
Maybe something like this: "Holds any errors encountered during processing of incoming data" ?
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>
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
|
ping? |
|
@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. |
|
Will take a look, but can @yanavlasov please review first and approve? |
jmarantz
left a comment
There was a problem hiding this comment.
I haven't gone over the tests in detail yet but here's a first pass.
| 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()); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
qq before I make this change (as it's pretty big) about where to factor this out to:
- utility function that takes codec and data. pros: will be simple to remove when exception removal.
- 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
innerDispatchand 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.
There was a problem hiding this comment.
Yeah +1 for (2) that sounds great.
There was a problem hiding this comment.
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
will fail unless I directly call theinnerDispatch 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 envoy/source/common/http/http2/codec_impl.cc
Line 893 in c86c679
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; }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I think you are still going to need this given early returns?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
test/integration/fake_upstream.h
Outdated
| 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()); | ||
| } |
There was a problem hiding this comment.
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?
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_); |
There was a problem hiding this comment.
I felt bad about writing this line. FYI.
There was a problem hiding this comment.
Can you maybe add a comment/TODO to reference exception removal for someone that comes across this?
mattklein123
left a comment
There was a problem hiding this comment.
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
source/common/http/codec_client.cc
Outdated
| codec_->dispatch(data); | ||
| } catch (CodecProtocolException& e) { | ||
| ENVOY_CONN_LOG(debug, "protocol error: {}", *connection_, e.what()); | ||
| Status status = codec_->dispatch(data); |
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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_); |
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
should we also catch EnvoyException to make sure there are no others that leak through?
or maybe even std::exception?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>
|
LGTM but might need a tidy fix. Nice work! /wait |
Signed-off-by: Asra Ali <asraa@google.com>
Signed-off-by: Asra Ali <asraa@google.com>
|
/azp run envoy-presubmit |
|
Azure Pipelines successfully started running 1 pipeline(s). |
yanavlasov
left a comment
There was a problem hiding this comment.
LGTM modulo minor nit. Thanks for getting this done.
| } | ||
|
|
||
| void ConnectionManagerImpl::handleCodecException(const char* error) { | ||
| void ConnectionManagerImpl::handleCodecException(absl::string_view error) { |
There was a problem hiding this comment.
Nit: make it handleCodecError ? Since it is not an exception anymore.
(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::StatusforConnection::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