feat(grpc): add server-side Listener and Transport traits - #2725
Conversation
a2a5591 to
4e31c84
Compare
| /// [`downcast_ref`](Any::downcast_ref). | ||
| /// | ||
| /// This is analogous to Go's `net.Addr` interface. | ||
| pub trait ListenerAddress: fmt::Display + Send + Sync + 'static { |
There was a problem hiding this comment.
Add TODO: consider Debug instead or whatever?
There was a problem hiding this comment.
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
| /// Enables downcasting to a concrete address type. | ||
| fn as_any(&self) -> &dyn Any; |
There was a problem hiding this comment.
Done. Also, Removed the as_any . It initially stemmed from not realizing our msrv had trait upcasting (1.86) present in our msrv.
|
|
||
| impl fmt::Display for UnixListenerAddress { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| write!(f, "{}", self.path) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Everything should now be consistent. Used newtype pattern for SocketAddr . PTAL if it looks okay.
| pub(crate) async fn serve_with_shutdown( | ||
| &self, | ||
| listener: &impl Listener, | ||
| signal: impl Future<Output = ()>, | ||
| ) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
4e31c84 to
a954565
Compare
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))
a954565 to
3699e26
Compare
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:
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:
Open Questions:
local_addrsmay need to be updated to be errorable(contrary to our previous discussion), tonic's API is errorable and we currently do an expect.shutdownper listener vsshutdownon server