Skip to content

xDS: add flow control fields to ext_proc protocol and filter config#45509

Open
markdroth wants to merge 5 commits into
envoyproxy:mainfrom
markdroth:xds_ext_proc_flow_control
Open

xDS: add flow control fields to ext_proc protocol and filter config#45509
markdroth wants to merge 5 commits into
envoyproxy:mainfrom
markdroth:xds_ext_proc_flow_control

Conversation

@markdroth

Copy link
Copy Markdown
Contributor

Commit Message: xDS: add flow control fields to ext_proc protocol and filter config
Additional Description: Add flow control fields to ext_proc protocol and filter config.
Risk Level: Low
Testing: N/A
Docs Changes: N/A
Release Notes: N/A
Platform Specific Features: N/A

CC @penguingao @tyxia @yanjunxiang-google @ejona86 @easwars @dfawley

Signed-off-by: Mark D. Roth <roth@google.com>
@repokitteh-read-only

Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @wbpcode
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #45509 was opened by markdroth.

see: more, trace.

Signed-off-by: Mark D. Roth <roth@google.com>
// which governs how much data can be sent.
//
// [#not-implemented-hide:]
message ClientWindowUpdate {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can the values also be zero? I assume one of them could and would be zero at a time incase of updating only one side window. But can both be zero? The HTTP RFC says that 0 size window update is considered protocol error, is it the same 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.

Setting both values to 0 would be a no-op, but I don't see any value in going out of our way to fail if it happens. I think we should be a little flexible here to avoid introducing edge cases in ext_proc server implementations.

Comment thread api/envoy/service/ext_proc/v3/external_processor.proto Outdated
@tyxia tyxia self-assigned this Jun 10, 2026
@yanjunxiang-google

yanjunxiang-google commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@markdroth I understand you added this protocol change to address a possible deadlock issue you identified. Do you want to first reproduce that deadlock issue with an integration test?

@yanjunxiang-google

Copy link
Copy Markdown
Contributor

/assign @yanavlasov

Co-authored-by: eshitachandwani <59800922+eshitachandwani@users.noreply.github.com>
Signed-off-by: Mark D. Roth <roth@google.com>

@markdroth markdroth left a comment

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 understand you added this protocol change to address a possible deadlock issue you identified. Do you want to first reproduce that deadlock issue with an integration test?

I don't feel the need to wait for a reproduction case in any particular ext_proc implementation. This is a protocol problem and needs to be addressed, independent of any individual implementation.

// which governs how much data can be sent.
//
// [#not-implemented-hide:]
message ClientWindowUpdate {

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.

Setting both values to 0 would be a no-op, but I don't see any value in going out of our way to fail if it happens. I think we should be a little flexible here to avoid introducing edge cases in ext_proc server implementations.

@wbpcode wbpcode left a comment

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.

is there any reason why cannot we reuse the HTTP2 self's flow control?

@markdroth

markdroth commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@wbpcode Depending only on HTTP/2 flow control can introduce deadlocks. For details, see the description in https://github.com/markdroth/proposal/blob/xds_ext_proc/A93-xds-ext-proc.md#flow-control (from grpc/proposal#484).

@botengyao

Copy link
Copy Markdown
Member

Please review, @yanavlasov, @yanjunxiang-google, @tyxia

@wbpcode wbpcode assigned wbpcode and unassigned wbpcode Jun 24, 2026

@yanavlasov yanavlasov left a comment

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.

Sorry for the delay with the review. This all makes sense. Asking some clarifying questions.

/wait-any

// send a message containing only this field.
//
// [#not-implemented-hide:]
ServerWindowUpdate server_window_update = 13;

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.

If I understand correctly, the server can send window_update without needing to send anything in the oneof response above?

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.

Yes. That's documented on lines 365-368 above.

// [#not-implemented-hide:]
message FlowControlInit {
// Downstream-to-sidestream initial window size.
int64 initial_window_downstream_to_sidestream = 1;

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 assumes initial size can be negative. Is it correct?

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.

Yeah, that's possible.

In practice, I don't think it's useful for the initial window size to be negative, since that has basically the same effect as setting it to zero. However, since this flow control design requires senders to be able to handle negative values in general, I don't see any reason to go out of our way to disallow them for the initial window size.

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.

Did we want to say anything in the doc about the initial sizes the senders can assume? The spec proposed 64 or 32Kb. This could be useful for implementer to know what peers assume as initial values.

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 added a note about this.

int64 window_increment_sidestream_to_upstream = 1;

// Window update for sidestream-to-downstream.
int64 window_increment_sidestream_to_downstream = 2;

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.

Are we allowing negative values? This would mean that sender budget could go negative. If this is allowed, it needs to be clarified in the comments.

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.

Yes, it's totally valid for a window update to be negative. This is documented on line 135 above.

// ones are set for a particular HTTP request/response depend on the
// processing mode.
oneof request {
option (validate.required) = true;

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.

Removing this might not be backward compatible.

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.

For example the existing servers may always evaluating a ProcessingRequest contains oneof these message types, and not including one will cause the server break.

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.

In practice, I don't think it will cause any problems. The initial message from the filter to the ext_proc server will always set one of the fields in this oneof, and for FULL_DUPLEX_STREAMED mode, the filter will enable flow control only if the ext_proc server sends back a window update. If the ext_proc server does send back a window update, then we know it supports flow control, so there won't be a problem; if it doesn't, then we assume it doesn't, in which case we will never send a message that doesn't populate this field.

Also, note that the PGV annotations aren't really that strict anyway:

  • The API versioning policy says that it's not safe to increase the strictness of these annotations. Decreasing the strictness (which is what we're doing here) is generally fine.
  • As per the xDS specification, the PGV annotations aren't really a formal part of the spec. They're essentially an implementation detail of Envoy when validating config resources. (gRPC does not support PGV, and none of our internal xDS-capable data planes do either.)
  • Even Envoy does not actually implement these validations for things other than xDS resources themselves. Since this is part of the ext_proc wire protocol, not part of an xDS resource, I don't think Envoy actually enforces this.

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.

Adding @adisuissa for his opinion on this

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.

It will be much easier for security reason that Envoy state machine expecting a finite number of message types:

switch (response->response_case()) {

That way, Envoy can easily deems certain message types are valid, and other types are spurious:

// Any other message is considered spurious

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 don't understand what benefit we get by requiring these fields to be mutually exclusive. Keep in mind that there is overhead involved in each message sent on the stream, and I think there are a lot of cases where it would make sense to combine multiple events into a single message. For example, in the case where the ext_proc server needs to see the body before responding to headers, it would be advantageous to send both headers and body in the same message. The oneof here prevents that from working.

It's not clear to me what benefit we're getting in exchange for that restriction. It seems like the Envoy code could easily handle that: instead of having a switch block, it could just check for each event being present individually.

// has sent a window update, thus indicating that it supports flow control.
//
// [#not-implemented-hide:]
ClientWindowUpdate client_window_update = 13;

@yanjunxiang-google yanjunxiang-google Jun 26, 2026

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.

I am thinking we should add this ClientWindowUpdate into the oneof so the WINDOW_UPDATE frames can be sent as a standalone message. And we do not need to support piggy-back WINDOW_UPDATE after other types.

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 think it's useful to be able to include a window update in the same message with header or body payload. I suspect that there will be situations where that will make a difference to performance.

Also, in general, oneof can cause all sorts of backward compatibility problems. That's why the xDS style guide explicitly says not to use it these days. I think we should try to avoid putting things in oneofs whenever possible.

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.

Envoy recently added kStreamedImmediateResponse message type (#42424) into this oneof.


// The response type that is sent by the server.
oneof response {
option (validate.required) = true;

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.

Same as above

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.

See my other replies. I think this is not going to cause any real problems, and I think it's important for our backward-compatibility story for FULL_DUPLEX_STREAMED mode.

@yanavlasov yanavlasov left a comment

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.

The change looks good to me. Just one small question about initial window sizes. LGTM otherwise.

/wait-any

// [#not-implemented-hide:]
message FlowControlInit {
// Downstream-to-sidestream initial window size.
int64 initial_window_downstream_to_sidestream = 1;

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.

Did we want to say anything in the doc about the initial sizes the senders can assume? The spec proposed 64 or 32Kb. This could be useful for implementer to know what peers assume as initial values.

// servers.
//
// [#not-implemented-hide:]
google.protobuf.Duration flow_control_init_timeout = 27;

@yanjunxiang-google yanjunxiang-google Jun 29, 2026

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.

I think we do not need to maintain a separate timer for this to keep it simpler. We can just use existing STREAM_IDLE_TIMEOUT to recover if the server response does not arrive in time.

The logic is this:
If the response does not come back in time,
Envoy continue sends ProcessingRequest carries data until the initial window size exhausted and stop sending any further, and eventually the stream idle timeout kicks in and destroyes the connections.

If the response does come back, then Envoy data flows through.

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.

If I'm understanding correctly, it seems like you're saying that we would simply fail the request instead of proceeding without flow control? That doesn't seem like a good failure mode.

Consider the case where the ext_proc server needs to see the body before it responds to the headers. Without flow control push-back, that works fine, because the filter can send the full payload, and then the ext_proc server can respond to the headers. However, if the body is larger than the initial window size and the ext_proc server does not support flow control, then the filter will not be able to send the full body, but the ext_proc server will not send the headers response until it sees the full body.

In that case, we want the filter to, after some timeout, assume that the ext_proc server does not support flow control, so that it can continue sending the rest of the body. We don't want the request to just fail.

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.

Oh, right, sounds good!

Signed-off-by: Mark D. Roth <roth@google.com>

@wbpcode wbpcode left a comment

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.

/lgtm api

@repokitteh-read-only repokitteh-read-only Bot removed the api label Jul 4, 2026
Comment thread api/envoy/service/ext_proc/v3/external_processor.proto Outdated
Signed-off-by: Mark D. Roth <roth@google.com>
@wbpcode wbpcode removed the api label Jul 12, 2026
@markdroth

Copy link
Copy Markdown
Contributor Author

/retest

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.

7 participants