Skip to content

feat: impl apply_on_stream_done for local rate limit - #48530

Open
rudrakhp wants to merge 8 commits into
envoyproxy:mainfrom
rudrakhp:local-ratelimit-stream-done
Open

rudrakhp wants to merge 8 commits into
envoyproxy:mainfrom
rudrakhp:local-ratelimit-stream-done

Conversation

@rudrakhp

@rudrakhp rudrakhp commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Commit Message: feat: impl apply_on_stream_done for local rate limit
Additional Description: Implement apply_on_stream_done feature for local rate limit, similar to the existing feature in global rate limit. Note that unlike in global rate limit, apply_on_stream_done does't apply to route/vhost level rate limit descriptors for local rate limit filter. For such behavior typed_per_filter_config can be used instead.
Risk Level: Low (new API)
Testing: Unit and Integration testing
Docs Changes: Yes
Release Notes: Yes
Platform Specific Features: N/A
Fixes #37790

@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 @markdroth
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #48530 was opened by rudrakhp.

see: more, trace.

Copilot AI 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.

🟡 Changes recommended

Concurrent partial consumption can incorrectly restore tokens in shared buckets, weakening rate-limit enforcement.

1 open issue
What changed in this PR

Adds apply_on_stream_done support to HTTP local rate limiting, addressing #37790 by accounting for resource usage available only after a response completes.

Changes:

  • Updates matching descriptor buckets at stream completion, including usage charges and refunds.
  • Adds unit and integration coverage for completion accounting and subsequent request enforcement.
  • Documents the behavior in API comments, configuration guidance, and release notes.
File Description
test/​extensions/​filters/​http/​local_ratelimit/​local_ratelimit_integration_test.cc Tests completed usage limiting subsequent requests.
test/​extensions/​filters/​http/​local_ratelimit/​filter_test.cc Tests completion accounting across filter configurations.
test/​extensions/​filters/​http/​local_ratelimit/​BUILD Adds the simulated-time test dependency.
test/​extensions/​filters/​common/​local_ratelimit/​local_ratelimit_test.cc Tests charges, refunds, wildcard descriptors, and sharing.
test/​extensions/​filters/​common/​local_ratelimit/​BUILD Adds the simulated-time test dependency.
source/​extensions/​filters/​http/​local_ratelimit/​local_ratelimit.h Declares completion handling and retained stream context.
source/​extensions/​filters/​http/​local_ratelimit/​local_ratelimit.cc Applies completion actions using response and stream information.
source/​extensions/​filters/​common/​local_ratelimit/​local_ratelimit_impl.h Declares descriptor accounting and partial consumption.
source/​extensions/​filters/​common/​local_ratelimit/​local_ratelimit_impl.cc Implements completed-usage bucket updates.
docs/​root/​configuration/​http/​http_filters/​local_rate_limit_filter.rst Explains completion accounting with a configuration example.
changelogs/​current/​new_features/​local_ratelimit__apply-on-stream-done.rst Announces local completion-accounting support.
api/​envoy/​extensions/​filters/​http/​local_ratelimit/​v3/​local_rate_limit.proto Documents completion actions in local filter configuration.
api/​envoy/​config/​route/​v3/​route_components.proto Documents local support for apply_on_stream_done.

Review effort: Balanced

@rudrakhp
rudrakhp force-pushed the local-ratelimit-stream-done branch from a636a95 to c38a5ae Compare October 22, 2026 07:24
@repokitteh-read-only

Copy link
Copy Markdown

CC @envoyproxy/runtime-guard-changes: FYI only for changes made to (source/common/runtime/runtime_features.cc).

🐱

Caused by: #48530 was synchronize by rudrakhp.

see: more, trace.

@rudrakhp
rudrakhp force-pushed the local-ratelimit-stream-done branch 2 times, most recently from 462f9d9 to e0a0404 Compare October 22, 2026 08:11
Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
@rudrakhp
rudrakhp force-pushed the local-ratelimit-stream-done branch from e0a0404 to 87d1e5e Compare October 22, 2026 08:26
@rudrakhp

Copy link
Copy Markdown
Member Author

/assign @wbpcode

@repokitteh-read-only repokitteh-read-only Bot assigned wbpcode and unassigned markdroth Oct 22, 2026

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

Thanks for this contribution. One high level comment to start it.

/wait-any

Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
@rudrakhp
rudrakhp requested a review from wbpcode October 26, 2026 04:51
…eam-done

Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>

# Conflicts:
#	source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc
#	test/extensions/filters/http/local_ratelimit/filter_test.cc

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

Thanks. The API LGTM now. Thanks for the contribution. And some more comments are added.

/wait

auto cb = [tokens, threshold, allow_partial](double total) {
if (allow_partial) {
return std::max(0.0, std::min(total, tokens));
}

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.

see, we actually could consumer future tokens. So, rather than allow_partial, should we support a overdraft here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Wanted to keep budget decisions within rate limit window, but makes sense if we can allow overdraft till max_tokens, basically one extra bucket.

if (used_config_->hasStreamDoneActions()) {
// Keep the request headers and the route that owns per-route config for onDestroy().
request_headers_ = &headers;
route_ = decoder_callbacks_->routeSharedPtr();

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 it's unnecessary to store the request_headers_ and route_ now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +261 to +262
// Stream-owned headers outlive onDestroy(), as in the global rate limit filter.
response_headers_ = &headers;

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.

we also needn't this? because we always could access the request headers and response headers from the stream callbacks if necessary in the onDestory(). And it's unsafe to keep reference of response headers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agree, done.

Comment on lines +284 to +291
// Only set when the used config's embedded rate limits have apply_on_stream_done entries.
if (request_headers_ == nullptr) {
return;
}
std::vector<RateLimit::Descriptor> descriptors;
used_config_->populateDescriptors(*request_headers_, response_headers_,
decoder_callbacks_->streamInfo(), descriptors,
/*on_stream_done=*/true);

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.

we also need to respect the enabled decision here:

we could add a bool reatelimit_eanbled_{false}; to indicate it, then:


# At the decodeHeaders():
ratelimit_enabled_ = used_config_->enabled();
if (!ratelimit_enabled_) {
    return Http::FilterHeadersStatus::Continue;
}

# At the onDestory()
if (!ratelimit_enabled_ || !used_config_->hasStreamDoneActions()) {
  return;
}
.....
``

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was already handled, request_headers_ was only stored after the enabled() check in decodeHeaders(), so onDestroy() returned early for disabled streams (covered by test DisabledOrNotDecodedDoesNotAccount).
Now that #48530 (comment) is addressed, it needs to be explicit like you said, done.

Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
@rudrakhp
rudrakhp requested a review from wbpcode November 4, 2026 18:22

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

local ratelimit: support apply_on_stream_done for local rate limit

4 participants