Skip to content

Conversation

@YutaoMa
Copy link
Collaborator

@YutaoMa YutaoMa commented Dec 10, 2025

Motivation

As explained previously #2459 , we are upstreaming an implementation of xDS client in Rust, in order to support xDS based routing and load balancing in tonic / gRPC Rust eventually. This PR is a continuation of the effort, by exposing all the expected public APIs (traits, types, function signatures) first to lay out the design.

Solution

Here is an overview of all the public APIs we are adding in this PR:

File Public Types
error.rs Error, Result
resource.rs Resource trait
transport.rs Transport, TransportStream, DiscoveryRequest, DiscoveryResponse
runtime.rs Runtime trait
config.rs ClientConfig
watcher.rs ResourceWatcher<T>, ResourceEvent<T>
client.rs XdsClient, XdsClientBuilder

In case the reviewer is curious if the provided APIs could support the expected functionality of an xDS client, I have a working proof of concept that implements these APIs in my fork: https://github.com/YutaoMa/tonic/tree/yutaoma/xds-client-poc, it showcases the APIs working together with an implementation based on the tonic stack (tonic, tokio, prost).

@LucioFranco
Copy link
Member

@YutaoMa CI is failing because we have a job that checks that we don't expose certain APIs publically so this should be addressed https://github.com/hyperium/tonic/actions/runs/20112284748/job/57712601774?pr=2464#step:7:69 ideally, we should not expose things publically that we don't own to avoid potential breaking changes in the future.

@YutaoMa
Copy link
Collaborator Author

YutaoMa commented Dec 15, 2025

@LucioFranco as suggested I've removed the dependency on the pre-1.0 futures crate and added bytes to metadata allowlist similar to other tonic crates.

Copy link
Collaborator

@gu0keno0 gu0keno0 left a comment

Choose a reason for hiding this comment

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

Some general questions, LGTM overall

Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

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

LGTM! Feel free to address things in follow ups, nice to see the start here!

/// }
/// }
/// ```
pub fn next(&mut self) -> impl Future<Output = Option<ResourceEvent<T>>> + '_ {
Copy link
Member

Choose a reason for hiding this comment

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

take a look at some of the tokio::sync apis for what this signature should look like in regards to async fn/return types etc in general tokio is a great reference point

/// A bidirectional stream for xDS ADS communication.
pub trait TransportStream: Send + 'static {
/// Send a discovery request to the server.
fn send(&mut self, request: DiscoveryRequest) -> impl Future<Output = Result<()>> + Send;
Copy link
Member

Choose a reason for hiding this comment

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

there might be a crate that provides macro sugar for the sendable async trait fn

Copy link
Collaborator

Choose a reason for hiding this comment

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

@YutaoMa YutaoMa requested a review from dfawley December 19, 2025 00:18
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.

Sorry for the late review. In general this looks great, thanks! I have a few changes to suggest, and feel free to punt on some of them until later.

Comment on lines +9 to +19
/// A discovery request to send to the xDS server.
#[derive(Debug, Clone)]
pub struct DiscoveryRequest {
// TODO: Add fields as needed
}

/// A discovery response from the xDS server.
#[derive(Debug, Clone)]
pub struct DiscoveryResponse {
// TODO: Add fields as needed
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it possible to leave the transport as a byte transport, or does that not work with tonic so well?

The main design idea is that it's able to send generic messages (but not have to say "these are 'proto messages'" since "what a proto message is" will likely change from prost to google-protobuf). So we serialize inside the xdsclient and send the serialized bytes to the transport. It's still transmitted as proto, it just doesn't need to do the serialization in tonic.

/// Creates a new bidirectional ADS stream to the xDS server.
///
/// This may be called multiple times for reconnection.
fn connect(&self) -> impl Future<Output = Result<Self::Stream>> + Send;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please rename this? Maybe to new_stream or create_stream? "Connect" implies there's a new TCP connection being made, but this just creates a stream.

/// }
/// }
/// ```
pub async fn next(&mut self) -> Option<ResourceEvent<T>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The other gRPC versions of this have a callback that is used to indicate the returned result has "finished being processed". Generally, what that means is: it's added a watch for all the related resources it think it will need. IIRC this was useful to batch these subscription requests as we block all ADS stream operations while calling the watchers. In Go we struggled for awhile with whether we should just say "the function blocks until processing is done" or "it's useful to pass this closure elsewhere to allow all watchers to be doing some async processing at the same time" and we eventually decided on the latter. In Rust this could be a oneshot channel or some equivalent that is just a signal that something is finished.

https://pkg.go.dev/google.golang.org/[email protected]/internal/xds/clients/xdsclient#ResourceWatcher

pub fn watch<T: Resource>(&self, _name: impl Into<String>) -> ResourceWatcher<T> {
todo!()
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

5 participants