Skip to content

feat(grpc): add server-side Listener and Transport traits - #2725

Open
sauravzg wants to merge 1 commit into
sauravz/dyn-server-credsfrom
sauravz/server-transport-api
Open

feat(grpc): add server-side Listener and Transport traits#2725
sauravzg wants to merge 1 commit into
sauravz/dyn-server-credsfrom
sauravz/server-transport-api

Conversation

@sauravzg

@sauravzg sauravzg commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Server-owned connection lifecycle and graceful shutdown:

  • Server accepts connections from a Listener and manages their lifecycle. Each accepted Transport is bound to a handler via Transport::serve(), which returns a GracefulConnection future that the Server polls and can shut down cooperatively.

  • Graceful shutdown follows a pattern combining the tonic Server API with hyper's graceful shutdown guide (https://hyper.rs/guides/0.14/server/graceful-shutdown/).

  • ListenerAddress provides a type-safe, downcastable address for listeners, analogous to Go's net.Addr.

Runtime API changes:

  • Runtime trait gains tcp_listener() and unix_listener() methods (doc(hidden)) that return EndpointListener (pub(crate)).
  • EndpointListener trait (pub(crate)) wraps accept() and local_addr() for server-side listening sockets.
  • Tokio implementations: TokioTcpListener, TokioUnixListener.
  • New address module (rt::address) with ListenerAddress trait and impls for SocketAddr and UnixListenerAddress.

Breaking changes:

  • Server::serve() now takes &impl Listener (sealed) instead of the previous ServerListener. The old ServerListener returned a pre-assembled ServerCall with constructed streams; the new Listener yields raw Transport objects and stream construction moves into Transport::serve().

  • serve_with_shutdown() is pub(crate) pending a design decision on whether the shutdown signal should be per-listener or per-server.

New public API surface:

  • Dyn versions of handler, recvstream (pub) - Same as client so should be okay
  • ListenerAddress trait (pub) — network(), as_any() - Needs to be public because it'll be exposed eventually
  • Listener trait (pub, sealed) — accept(), local_addr()
  • InMemoryServingConnection (pub)
  • ListenerAddress impl for std::net::SocketAddr
  • Runtime::tcp_listener(), Runtime::unix_listener() (doc(hidden))
  • Transport trait (pub, doc(hidden), private token) — serve()
  • GracefulConnection trait (pub, doc(hidden), private token) — graceful_shutdown()
  • UnixListenerAddress (pub(crate))
  • InMemoryListenerAddress (crate-private)
  • EndpointListener trait (pub(crate))

Open Questions:

  • local_addrs may need to be updated to be errorable(contrary to our previous discussion), tonic's API is errorable and we currently do an expect.
  • shutdown per listener vs shutdown on server
  • The use of associated types creates annoying issues of sealing and private token since dependent types need to be made public.

@sauravzg
sauravzg requested a review from dfawley July 8, 2026 04:12
@sauravzg
sauravzg force-pushed the sauravz/server-transport-api branch from a2a5591 to 4e31c84 Compare July 16, 2026 11:21
Comment thread grpc/src/rt/address.rs Outdated
/// [`downcast_ref`](Any::downcast_ref).
///
/// This is analogous to Go's `net.Addr` interface.
pub trait ListenerAddress: fmt::Display + Send + Sync + 'static {

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.

Add TODO: consider Debug instead or whatever?

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've updated the trait to derive debug and also inherit Display, mostly along the lines of debug for swe debugging and display for end user display.

LMK if you'd like me to just keep debug

Comment thread grpc/src/rt/address.rs Outdated
Comment on lines +40 to +41
/// Enables downcasting to a concrete address type.
fn as_any(&self) -> &dyn Any;

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.

Add Any as supertrait instead?

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.

Done. Also, Removed the as_any . It initially stemmed from not realizing our msrv had trait upcasting (1.86) present in our msrv.

Comment thread grpc/src/rt/address.rs Outdated

impl fmt::Display for UnixListenerAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.path)

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.

This should probably say unix:? Or inmemory can skip it? I'm not sure. But we should be consistent. I think it probably does belong here, though.

Note that if we did the former, then SocketAddr would be wrong, in which case maybe we need a newtype for SocketAddr so that we can define Display how we want.

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.

Everything should now be consistent. Used newtype pattern for SocketAddr . PTAL if it looks okay.

Comment thread grpc/src/server/mod.rs
Comment on lines +189 to +193
pub(crate) async fn serve_with_shutdown(
&self,
listener: &impl Listener,
signal: impl Future<Output = ()>,
) {

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 there's another interesting question here, which is: who runs the task that polls the serving future?

It could be the application, like this is (and your example in the design doc shows). But instead it could be the grpc library that uses the runtime to fork a task for it. In the case of the latter it would have to return an object with a graceful_shutdown method and would need a Drop impl to cancel the task.

I'm not sure of all the pros/cons for these two options, but it's probably worth spending some time thinking about.

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.

Are there any benefits to grpc polling the future? The extra task is the additional con for me right now.

I haven't thought about it either but the approach is consistent with what tonic, hyper and axum do. I can't immediately think of a pro right away for creating a new task.

I can take a look at other grpc implementations to see what they do , but IIUC there are probably two patterns start + wait(likely java, cpp , python ) and serve(go) . At a high level our approach to have the application drive it seems close-ish to them from rust.

Is there any particular advantage of having grpc drive things?

Additionally, updated to take listener by value as discussed in the design.

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.

The extra task is the additional con for me right now.

Isn't it just a matter of who makes the task? Either way there's a task per listener? Or, I guess if the application owns it, then it's a future, and maybe one task can poll multiple futures? Is that worth worrying about though?

The advantage of grpc owning it would be: if a library wants to start a grpc server, it would need to figure out what runtime to use to spawn the task. grpc is already configured with the runtime by the application, so it already knows how to do this.

I don't know if this is a good enough reason to change it, but I think it's worth thinking about the implications of the different paths.

@sauravzg
sauravzg force-pushed the sauravz/server-transport-api branch from 4e31c84 to a954565 Compare July 30, 2026 12:41
Server-owned connection lifecycle and graceful shutdown:

- Server accepts connections from a Listener and manages their
  lifecycle. Each accepted Transport is bound to a handler via
  Transport::serve(), which returns a GracefulConnection future
  that the Server polls and can shut down cooperatively.

- Graceful shutdown follows a pattern combining the tonic Server
  API with hyper's graceful shutdown guide
  (https://hyper.rs/guides/0.14/server/graceful-shutdown/).

- ListenerAddress provides a type-safe, downcastable address for
  listeners, analogous to Go's net.Addr.

Runtime API changes:

- Runtime trait gains tcp_listener() and unix_listener() methods
  (doc(hidden)) that return EndpointListener (pub(crate)).
- EndpointListener trait (pub(crate)) wraps accept() and
  local_addr() for server-side listening sockets.
- Tokio implementations: TokioTcpListener, TokioUnixListener.
- New address module (rt::address) with ListenerAddress trait
  and impls for SocketAddr and UnixListenerAddress.

Breaking changes:

- Server::serve() now takes &impl Listener (sealed) instead of
  the previous ServerListener. The old ServerListener returned a
  pre-assembled ServerCall with constructed streams; the new
  Listener yields raw Transport objects and stream construction
  moves into Transport::serve().

- serve_with_shutdown() is pub(crate) pending a design decision
  on whether the shutdown signal should be per-listener or
  per-server.

New public API surface:

- Listener trait (pub, sealed) — accept(), local_addr()
- Transport trait (pub, doc(hidden), private token) — serve()
- GracefulConnection trait (pub, doc(hidden), private token)
  — graceful_shutdown()
- ListenerAddress trait (pub) — network(), as_any()
- ListenerAddress impl for std::net::SocketAddr
- UnixListenerAddress (pub(crate))
- InMemoryServingConnection (pub)
- InMemoryListenerAddress (crate-private)
- EndpointListener trait (pub(crate))
- Runtime::tcp_listener(), Runtime::unix_listener() (doc(hidden))
@sauravzg
sauravzg force-pushed the sauravz/server-transport-api branch from a954565 to 3699e26 Compare July 30, 2026 13:05
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.

2 participants