Skip to content

grpc: add testing utilities for LB policy tests #2380

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

Merged
merged 5 commits into from
Aug 12, 2025

Conversation

easwars
Copy link
Contributor

@easwars easwars commented Aug 7, 2025

This PR adds a couple of utility types that will help with testing LB policies at their API level.

This was originally part of #2340, and has been split out here to unblock other LB policy PR reviews.

@easwars
Copy link
Contributor Author

easwars commented Aug 7, 2025

Copy link
Collaborator

@dfawley dfawley left a comment

Choose a reason for hiding this comment

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

Mostly LGTM, just a few minor things.

}

pub(crate) struct TestWorkScheduler {
pub tx_events: mpsc::UnboundedSender<TestEvent>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: This should be pub(crate) since the struct itself is pub(crate). Same for other struct fields in this file.

Copy link
Contributor 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 118 to 120
// A test channel controller that forwards calls to a channel. This allows
// tests to verify when a channel controller is asked to create subchannels or
// update the picker.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Only /// comments show up in the struct docs. This seems like a doc comment, so it should use ///.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thanks.

impl Eq for TestSubchannel {}

pub(crate) enum TestEvent {
NewSubchannel(Address, Arc<dyn Subchannel>),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that the Subchannel trait has a method to return the Address. Is it possible to use that and avoid passing the Address separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}

impl ForwardingSubchannel for TestSubchannel {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not related to this PR: Should ForwardingSubchannel be a struct instead of a trait?

In the regular delegating pattern, all the structs implement a single interface. What is the intended use of the delegate() method? If we need to get the underlying internal Subchannel created by the channel, we could achieve this by adding an get_internal() method on the Subchannel trait returns an internal subchannel.

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 most important reason for going with what we have currently (i.e. a Subchannel trait that is not implementable and a ForwardingSubchannel trait that is implementable) is for us to be able to add new methods to the Subchannel trait without breaking users. And making the ForwardingSubchannel a trait allows us (and users in the future) to override certain functionality when they are wrapping subchannels. My understanding is that you cannot embed a struct like in Go and override only certain methods in Rust.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see. I have a follow-up question: why does Subchannel need to be a trait instead of a struct?

A struct with private fields and no public constructor can't be instantiated outside the module.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want LB policies in the tree to be able to wrap subchannels and override some of its functionality.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To clarify, I was asking about the Subchannel being a trait, not the ForwardingSubchannel which can be implemented by LB policies.

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 is the Subchannel trait that is returned from methods like new_subchannel, subchannel_update, pick etc. And the implementation for it is provided by the ExternalSubchannel struct in the channel, which refers to an InternalSubchannel that contains the actual subchannel functionality. In tests, we could use a TestSubchannel which would also again implement the Subchannel trait.

The ForwardingSubchannel need not be implemented by all LB policies. It will be required only by LB policies that want to wrap subchannels. I can imagine this being a struct with a blanket implementation for the Subchannel trait. But I'm not able to imagine the Subchannel itself being a struct. Maybe we can discuss this if we have time during one of our meetings?

Comment on lines 26 to 29
load_balancing::{
ChannelController, ExternalSubchannel, ForwardingSubchannel, LbState, Subchannel,
WorkScheduler,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Based on Lucio's comments on Doug's and mine PRs, we should follow tonic's import style. We should keep only one level of nesting so that every use statement is in a single line. Here, it would mean creating a new line for

use crate::client::load_balancing::{ChannelController, ExternalSubchannel, ForwardingSubchannel, LbState, Subchannel, WorkScheduler};

Copy link
Member

Choose a reason for hiding this comment

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

You can look into rustfmt toml settings I think we can enforce it there as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've manually edited the imports for now. I'll figure out the appropriate settings in a bit and maybe add it in a separate PR since it will end up modifying other files as well.

@arjan-bal arjan-bal removed their assignment Aug 8, 2025
@LucioFranco
Copy link
Member

Can rebase this now, CI is fixed on master

@easwars
Copy link
Contributor Author

easwars commented Aug 8, 2025

Can rebase this now, CI is fixed on master

Done. I hope it doesn't completely mess with the review comments' positioning on the files.

@dfawley
Copy link
Collaborator

dfawley commented Aug 8, 2025

Done. I hope it doesn't completely mess with the review comments' positioning on the files.

FWIW, doing a merge upstream/master instead avoids those kinds of problems.

@dfawley dfawley merged commit 7662376 into hyperium:master Aug 12, 2025
20 checks passed
cjqzhao pushed a commit to cjqzhao/tonic that referenced this pull request Aug 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants