-
Notifications
You must be signed in to change notification settings - Fork 84
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
filters: support new platform ResumeIteration status #1100
Conversation
3691127
to
e9f63f8
Compare
e9f63f8
to
58e21e9
Compare
5499521
to
3ffd978
Compare
Description: Adds internal buffering support for platform filters. Filters may now utilize Envoy's internal buffering by returning the status StopIterationAndBuffer. The complete buffer will be passed on subsequent on*Data invocations until iteration is resumed. This change does not support modifying the buffer (support coming in #1100). Risk Level: Moderate Testing: Unit coverage, local and CI Signed-off-by: Mike Schore <mike.schore@gmail.com>
58e21e9
to
6bf647a
Compare
Will be adding additional coverage, but implementation is ready for review. |
b1be28b
to
6c9c47c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some initial questions about the core.
I actually would like @rebello95 and @buildbreaker to review this, as we are getting to more and more complicated implementations and I'd like all of us to understand.
// Creating a new return status like this is brittle, but at least somewhat more resilient to | ||
// a new status being added in Envoy, since it won't overlap as long as the new status is added | ||
// rather than prepended. | ||
const envoy_filter_headers_status_t kEnvoyFilterHeadersStatusResumeIteration = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, at first glance I am a little confused about this status vs continue, which in envoy has the semantics of continuing, but also resuming if previously stopped. Perhaps it will become clear as I read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After looking at onData, I am still confused why we need two different statuses rather than driving difference via the presence of absence of headers to replace (or headers/data in onTrailers). I might just be being dense, but I wonder if we can eliminate the need of another status to support the use case?
If we do need the new status, can we write more documentation about it in the filter.cc file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I see? In continue iteration the buffer copied is only the current frame, but in resume iteration it is the entire internal buffer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@junr03 I think it will be helpful for you to reference the API doc we put together back when we discussed our implementation for advanced filters. It lays out differences between Envoy's native filters and the platform filters. Also the docstrings on the platform implementations of these APIs contain some detail as well, e.g.
https://github.com/lyft/envoy-mobile/blob/main/library/swift/src/filters/AsyncRequestFilter.swift
https://github.com/lyft/envoy-mobile/blob/main/library/swift/src/filters/FilterResumeStatus.swift
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry actually that's the other side of the coin - I mixed up the PRs because I'm working on the asynchronous side right now. For the synchronous side there's this (e.g.):
https://github.com/lyft/envoy-mobile/blob/main/library/swift/src/filters/FilterDataStatus.swift
Because iteration may have been stopped on headers, we need a mechanism for the platform filter to still inform Envoy of what modifications, if any, it wants to perform on headers when iteration is resumed. Same for headers and data, when resumption occurs on trailers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of the reason to have two statuses is that it keeps things simple for the main/golden path. When all you're doing is continuing, you never need to worry about sending previous entities.
@@ -10,7 +10,7 @@ api_proto_package() | |||
envoy_cc_library( | |||
name = "platform_bridge_filter_lib", | |||
srcs = [ | |||
"c_types.cc", | |||
"c_type_definitions.h", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change this file to another header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible to switch on externed constants without compile-time visibility into their defined values. Making this a header allows me to import their actual definitions as part of the only file that needs them at compile time - the filter implementation.
@@ -56,6 +57,16 @@ void PlatformBridgeFilter::onDestroy() { | |||
platform_filter_.instance_context = nullptr; | |||
} | |||
|
|||
void PlatformBridgeFilter::replaceHeaders(Http::HeaderMap& headers, envoy_headers c_headers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should put this in the header utility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The standard request/response path produces new headers; this is the only place we modify them, and moreover, need to modify them in place. I guess I'm not opposed to it, but I don't see an obvious use for it elsewhere at the moment.
return onData(data, end_stream, internal_buffer, platform_filter_.on_response_data); | ||
auto status = onTrailers(trailers, internal_buffer, &pending_request_headers_, | ||
platform_filter_.on_request_trailers); | ||
if (status == Http::FilterTrailersStatus::StopIteration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused who would flush this, since a particular filter should not have any calls past trailers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filters can stop iteration on trailers and still asynchronously resume iteration later. Consider for instance a filter that buffers an entire request, then kicks off some async side request, then resumes iteration when that side request completes. (We actually internally have some examples of exactly this.)
return Http::FilterHeadersStatus::StopIteration; | ||
|
||
default: | ||
PANIC("invalid filter state: unsupported status for platform filters"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this to NOT_REACHED_GCOVR_EXCL_LINE? We can add a new macro for NOT_REACHED that takes a string if we want to modify the message. Same with everywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that strictly correct though? I mean this could be reached - we receive the value from a public API.
case Http::FilterDataStatus::Continue: | ||
|
||
switch (result.status) { | ||
case kEnvoyFilterDataStatusContinue: | ||
if (iteration_state_ == IterationState::Stopped) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having the if and the panic we could just RELEASE_ASSERT(iteration_state_ != IterationState::Stopped, msg)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're absolutely right.
data.drain(data.length()); | ||
data.addBufferFragment(*Buffer::BridgeFragment::createBridgeFragment(result.data)); | ||
case kEnvoyFilterDataStatusResumeIteration: | ||
if (iteration_state_ != IterationState::Stopped) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above with the release assert
PANIC("invalid filter state: ResumeIteration may only be used when filter iteration is " | ||
"stopped"); | ||
} | ||
if (result.extra_headers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are extra headers only allowed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're only allowed where pending headers might still exist. That means onData or onTrailers (or async onResume) when iteration has been previously stopped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me after adding inline documentation about behavior (similar to what we have on the platform layer) and addressing Jose's other comments.
envoy_headers* extra_headers; | ||
envoy_data* extra_data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think adding inline documentation as to how these are used (similarly to how the values are documented on the platform layer's interfaces) would be helpful for future reference when reading this code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline documentation coming.
library/common/extensions/filters/http/platform_bridge/filter.cc
Outdated
Show resolved
Hide resolved
internal_buffer->addBufferFragment(*Buffer::BridgeFragment::createBridgeFragment(result.data)); | ||
return Http::FilterDataStatus::Continue; | ||
|
||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth having this be exhaustive so that if upstream Envoy adds a new iteration state Envoy Mobile won't compile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately we won't be able to get these compile-time guarantees here regardless. C/C++ don't enforce this, and static analysis would miss it too, since we're iterating over a constant set only partially defined in terms of the internal enum (ResumeIteration doesn't map to anything internal).
library/common/extensions/filters/http/platform_bridge/filter.cc
Outdated
Show resolved
Hide resolved
library/common/extensions/filters/http/platform_bridge/filter.cc
Outdated
Show resolved
Hide resolved
library/common/extensions/filters/http/platform_bridge/filter.cc
Outdated
Show resolved
Hide resolved
bool end_stream) { | ||
// Delegate to shared implementation for request and response path. | ||
return onHeaders(headers, end_stream, platform_filter_.on_response_headers); | ||
return onData(data, end_stream, internal_buffer, &pending_response_headers_, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is passing a null buffer here acceptable? (I assume yes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but good eye for catching the possibility. Here, nullptr
represents "none".
Http::HeaderMap* pending_request_headers_{}; | ||
Http::HeaderMap* pending_response_headers_{}; | ||
Http::HeaderMap* pending_request_trailers_{}; | ||
Http::HeaderMap* pending_response_trailers_{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't see that these references are being cleared - are we assuming they'll be automatically released when the filter is deallocated and that's sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These member pointers are all being initialized to nullptr
. This syntax is idiomatic (though it's also newer and perhaps less obvious to an unfamiliar observer than = nullptr;
). When they're set to something else in the implementation, they're being set to point to an object they don't manage - something else owns and will clean up that object. (This may be inferred by the type signatures that provide those objects.) The actual space the pointer itself occupies will be automatically reclaimed when the object is destructed.
(Don't worry - these details and their syntax are not all that intuitive.)
Thanks for the review @rebello95! |
… HTTP entities 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>
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>
88b0eaa
to
09088e7
Compare
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>
Description: Adds support for resuming iteration to platform filters via a new *ResumeIteration filter status. The API for platform filters requires all HTTP entities that should be forwarded to be attached to a result status of a filter invocation. When iteration is stopped, previous entities that were not forwarded may now be attached to the special ResumeIteration status, e.g., pending headers might be attached to ResumeIteration returned during an on-data invocation. See code documentation for additional detail. Full API documentation will be included in an upcoming PR.
Risk Level: Moderate
Testing: Unit tests
Signed-off-by: Mike Schore mike.schore@gmail.com