Skip to content

xds/resolver: ensure OnCommitted is invoked on early stream creation failure to prune unreferenced clusters. - #9140

Merged
Pranjali-2501 merged 27 commits into
grpc:masterfrom
Pranjali-2501:newstream-changes
Jul 28, 2026
Merged

xds/resolver: ensure OnCommitted is invoked on early stream creation failure to prune unreferenced clusters.#9140
Pranjali-2501 merged 27 commits into
grpc:masterfrom
Pranjali-2501:newstream-changes

Conversation

@Pranjali-2501

@Pranjali-2501 Pranjali-2501 commented May 25, 2026

Copy link
Copy Markdown
Contributor

This PR handles the cluster ref count leaks in case of stream creation failure.

When an xDS ConfigSelector selects a cluster or cluster specifier plugin for an RPC, it increments the cluster's reference count to keep the cluster active in the Service Config while the RPC is in flight. Previously, if stream creation failed early (e.g., aborted inside an xDS HTTP filter or interceptor before the stream was committed), or in OnCommitted would never be called, leaving the cluster reference count permanently elevated and preventing old/removed clusters from being pruned from the Service Config.

This PR ensures OnCommitted is reliably invoked even when stream creation fails early, and makes the callback idempotent so cluster reference counts are decremented exactly once per RPC.

Key Changes

stream.go (newClientStream):

  • Automatically append an OnFinish CallOption wrapping rpcConfig.OnCommitted whenever OnCommitted is configured by the ConfigSelector.
  • Ensure opts are properly passed through newStream when interceptors are present so OnFinish runs if stream creation aborts early.

internal/xds/resolver/serviceconfig.go (configSelector.SelectConfig):

  • Wrap config.OnCommitted with sync.Once so that reference count decrements are idempotent across both normal stream commitment (onCommit) and stream termination (OnFinish calloption).
  • Reliably trigger unsubscribe() / sendNewServiceConfig() when a cluster or plugin's reference count drops to zero.

RELEASE NOTES:

  • xds/resolver: fix cluster reference count leaks by ensuring cluster counts are correctly decremented via OnFinish even if stream creation fails early.

@Pranjali-2501 Pranjali-2501 added this to the 1.82 Release milestone May 25, 2026
@Pranjali-2501 Pranjali-2501 added Type: Feature New features or improvements in behavior Area: xDS Includes everything xDS related, including LB policies used with xDS. labels May 25, 2026
@codecov

codecov Bot commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.09%. Comparing base (2a112a8) to head (654109c).
⚠️ Report is 25 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9140      +/-   ##
==========================================
- Coverage   83.33%   83.09%   -0.25%     
==========================================
  Files         420      422       +2     
  Lines       34028    34862     +834     
==========================================
+ Hits        28358    28969     +611     
- Misses       4253     4395     +142     
- Partials     1417     1498      +81     
Files with missing lines Coverage Δ
internal/xds/resolver/serviceconfig.go 91.73% <100.00%> (ø)
stream.go 82.46% <100.00%> (+0.50%) ⬆️

... and 48 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread internal/resolver/config_selector.go Outdated
@Pranjali-2501 Pranjali-2501 changed the title xds: propagate call options to HTTP filter interceptor's NewStream. xds/resolver: remove onCommitted() from RPCConfig. Jun 4, 2026
Comment thread internal/xds/resolver/xds_resolver.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
}

func (icc *interceptingClientConn) UpdateState(state resolver.State) error {
return icc.ClientConn.UpdateState(state)

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.

Nit : we can remove this function since clientconn is already embedded and this is just passthrough function.

Comment thread test/xds/xds_resolver_e2e_test.go Outdated
// been prematurely removed.
select {
case js := <-jsonCh:
compareJSONConfigs(t, js, wantServiceConfig(clusterA, clusterB))

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.

Nit: I think we should return error from here , since config update is not expected.

Comment thread test/xds/xds_resolver_e2e_test.go Outdated
}

// Create an intercepting resolver builder.
jsonCh := make(chan string, 10)

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.

Nit: Do we need a channel of size 10 ?

@eshitachandwani eshitachandwani 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 with some nits.

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

LGTM. Validated the approach and test scenarios solve the usecases we need.

@Pranjali-2501

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors the management of cluster and plugin reference counts during RPC execution, ensuring that the OnCommitted callback is idempotent and executed on stream termination even if stream creation fails early. It also introduces comprehensive end-to-end tests for delayed cluster removal. Feedback on the changes points out a potential source of test flakiness in the new end-to-end test due to non-deterministic channel selection when unblocking concurrent RPCs, and suggests a robust way to handle either RPC finishing first.

Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread internal/xds/resolver/serviceconfig.go Outdated
Comment thread internal/xds/resolver/serviceconfig.go
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
}

// Create an intercepting resolver builder.
jsonCh := make(chan string, 4)

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.

Why do we need a buffer of 4 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.

We can go with a buffer of 3 here.

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.

We only need a buffer of 1. The test is reading items out of the channel before the next item is enqueued. So, I don't understand why it needs to have a buffer of anything greater than 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 applies to other places where this channel is defined as well.

Comment thread test/xds/xds_resolver_e2e_test.go
Comment thread test/xds/xds_resolver_e2e_test.go
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
@easwars easwars assigned Pranjali-2501 and unassigned easwars Jul 15, 2026
@Pranjali-2501
Pranjali-2501 requested a review from easwars July 27, 2026 13:21
Comment thread test/xds/xds_resolver_e2e_test.go Outdated
}

// Create an intercepting resolver builder.
jsonCh := make(chan string, 4)

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.

We only need a buffer of 1. The test is reading items out of the channel before the next item is enqueued. So, I don't understand why it needs to have a buffer of anything greater than 1.

Comment thread test/xds/xds_resolver_e2e_test.go Outdated
}

// Create an intercepting resolver builder.
jsonCh := make(chan string, 4)

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 applies to other places where this channel is defined as well.

@easwars easwars assigned Pranjali-2501 and unassigned easwars Jul 27, 2026
@easwars

easwars commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Please handle the minor nit in the test and please add a release note before merging.

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

Labels

Area: xDS Includes everything xDS related, including LB policies used with xDS. Type: Feature New features or improvements in behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants