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

filters: support new platform ResumeIteration status #1100

Merged
merged 21 commits into from
Sep 30, 2020
Merged

Conversation

goaway
Copy link
Contributor

@goaway goaway commented Sep 16, 2020

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

@goaway goaway force-pushed the ms/filters-resumption branch from 3691127 to e9f63f8 Compare September 18, 2020 13:55
@goaway goaway changed the base branch from main to ms/filter-buffering September 18, 2020 13:56
@goaway goaway force-pushed the ms/filters-resumption branch from e9f63f8 to 58e21e9 Compare September 18, 2020 13:57
@goaway goaway force-pushed the ms/filter-buffering branch from 5499521 to 3ffd978 Compare September 22, 2020 02:44
Base automatically changed from ms/filter-buffering to main September 22, 2020 04:52
goaway added a commit that referenced this pull request Sep 22, 2020
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>
@goaway goaway force-pushed the ms/filters-resumption branch from 58e21e9 to 6bf647a Compare September 22, 2020 05:06
@goaway goaway marked this pull request as ready for review September 22, 2020 14:38
@goaway
Copy link
Contributor Author

goaway commented Sep 22, 2020

Will be adding additional coverage, but implementation is ready for review.

@goaway goaway requested a review from junr03 September 22, 2020 14:40
@goaway goaway force-pushed the ms/filters-resumption branch from b1be28b to 6c9c47c Compare September 22, 2020 14:56
Copy link
Member

@junr03 junr03 left a 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 =
Copy link
Member

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.

Copy link
Member

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?

Copy link
Member

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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",
Copy link
Member

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?

Copy link
Contributor Author

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) {
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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?

Copy link
Contributor Author

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");
Copy link
Member

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.

Copy link
Contributor Author

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) {
Copy link
Member

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)

Copy link
Contributor Author

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) {
Copy link
Member

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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

rebello95
rebello95 previously approved these changes Sep 26, 2020
Copy link
Contributor

@rebello95 rebello95 left a 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.

Comment on lines 67 to 68
envoy_headers* extra_headers;
envoy_data* extra_data;
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Inline documentation coming.

internal_buffer->addBufferFragment(*Buffer::BridgeFragment::createBridgeFragment(result.data));
return Http::FilterDataStatus::Continue;

default:
Copy link
Contributor

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

Copy link
Contributor Author

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).

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_,
Copy link
Contributor

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)

Copy link
Contributor Author

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".

Comment on lines +71 to +74
Http::HeaderMap* pending_request_headers_{};
Http::HeaderMap* pending_response_headers_{};
Http::HeaderMap* pending_request_trailers_{};
Http::HeaderMap* pending_response_trailers_{};
Copy link
Contributor

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?

Copy link
Contributor Author

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.)

library/objective-c/EnvoyBridgeUtility.h Outdated Show resolved Hide resolved
@goaway
Copy link
Contributor Author

goaway commented Sep 28, 2020

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>
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>
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>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
buildbreaker
buildbreaker previously approved these changes Sep 30, 2020
Signed-off-by: Mike Schore <mike.schore@gmail.com>
Signed-off-by: Mike Schore <mike.schore@gmail.com>
@goaway goaway merged commit 1e6eeae into main Sep 30, 2020
@goaway goaway deleted the ms/filters-resumption branch September 30, 2020 13:28
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