Skip to main content

GrpcDispatcher

Struct GrpcDispatcher 

Source
pub struct GrpcDispatcher { /* private fields */ }
Expand description

gRPC dispatcher that routes A2A requests to a RequestHandler.

Serves the canonical lf.a2a.v1.A2AService (protobuf-native, wire compatible with the official A2A SDKs). That is the only gRPC surface: the deprecated pre-0.7 JSON-tunnel service, and the grpc-legacy-json feature that gated it, were removed in 0.8.

Create via GrpcDispatcher::new and serve with GrpcDispatcher::serve or build a tonic service with GrpcDispatcher::into_service.

Implementations§

Source§

impl GrpcDispatcher

Source

pub const fn new( handler: Arc<RequestHandler>, config: GrpcConfig, ) -> GrpcDispatcher

Creates a new gRPC dispatcher wrapping the given handler.

Source

pub const fn with_max_connections(self, max: usize) -> GrpcDispatcher

Caps the connections served at once. Default: unbounded.

The same knob the WebSocket binding has had since 2026-08-19 (with_max_connections), and the gap the measurement under with_http2_keepalive left open: keepalive closes a peer that stops answering, and nothing bounded how many peers could be answering at once. tonic’s server has no connection ceiling of its own — concurrency_limit is per connection — so this one is enforced in front of it: the permit is taken before accept(), and load past the ceiling waits in the kernel’s listen backlog until a served connection ends, or is refused by the kernel when that fills. The permit travels with the accepted socket and is released when tonic drops it, so it covers TLS, HTTP/2 and the RPCs on it alike.

Applied by every serve* method. into_service hands the service to a server the caller runs, and that server’s accept loop is the caller’s to bound.

Source

pub fn with_tls(self, tls: ServerTlsConfig) -> GrpcDispatcher

Serves TLS on the listener itself instead of relying on a terminating proxy. Default: off (plaintext).

tls carries the server certificate and key (ServerTlsConfig::new() .identity(Identity::from_pem(cert, key))) and, for mutual TLS, the CA that client certificates must chain to (.client_ca_root(..), with .client_auth_optional(true) to admit clients that present none). The types are re-exported from dispatch::grpc so no tonic dependency is needed. Applied by every serve* method and by nothing else: into_service hands the service to a tonic::transport::Server the caller configures, TLS included.

The listener then speaks TLS only. A plaintext client is refused at the handshake; there is no fallback, for the same reason the client’s grpc-tls transport refuses to downgrade.

tonic builds the acceptor from the process-level rustls crypto provider. If none is installed when the listener is built — the case for a binary that links both ring and aws-lc-rs, which this workspace’s --all-features build does — ring is installed as the default, once, rather than letting rustls panic. An application that wants a different provider installs it before serving.

A configuration tonic rejects (a key that does not match its certificate, an unparsable PEM) is reported by the serve* call as an std::io::Error, not a panic.

Source

pub const fn with_http2_keepalive( self, interval: Duration, timeout: Duration, ) -> GrpcDispatcher

Sends an HTTP/2 PING every interval on an idle connection and closes it if no answer arrives within timeout. Default: off.

GrpcConfig bounds message size and per-connection concurrency, and nothing bounded a connection that is simply there. MEASURED 2026-08-19: 400 TCP connections opened against this dispatcher and left silent were all accepted and all still alive twelve seconds later. The ceiling is the process’s file-descriptor table — the same measurement, and the same shape, as the WebSocket dispatcher before with_idle_timeout.

HTTP/2 keepalive is the gRPC-native answer to it, and it is better than a plain idle timeout for the same reason the WebSocket knob pings: a conformant client’s HTTP/2 stack answers a PING without the application being involved, so this closes peers that are unresponsive rather than peers that are merely quiet. A streaming RPC that is waiting for its next event is quiet and healthy, and this leaves it alone.

Off by default, matching this workspace’s other connection knobs: a deployment may have clients that are configured to object to PINGs, and choosing an interval for someone is choosing their traffic profile. (Duration::from_secs(30), Duration::from_secs(10)) is a conventional starting point.

Source

pub const fn with_max_connection_age(self, age: Duration) -> GrpcDispatcher

Closes a connection once it has been open for age, letting the client reconnect. Default: off.

Distinct from with_http2_keepalive, which detects a peer that has stopped answering. This one bounds a peer that answers perfectly and simply never leaves — which is what makes a fleet behind a load balancer drift into imbalance, since a gRPC client pins one connection and keeps it.

tonic sends GOAWAY and drains in-flight RPCs rather than cutting them, so this is a reconnect rather than a failure.

Source

pub const fn with_completion_grace(self, grace: Duration) -> GrpcDispatcher

How long in-flight tasks get to finish on their own once serve_with_shutdown’s signal fires, before they are cancelled. Default DEFAULT_COMPLETION_GRACE; the HTTP server’s ServeConfig::with_completion_grace is the same setting.

Source

pub const fn with_task_grace(self, grace: Duration) -> GrpcDispatcher

How long cancelled tasks get to act on their cancellation before the drain starts. Default DEFAULT_TASK_GRACE.

Source

pub const fn with_drain_timeout(self, timeout: Duration) -> GrpcDispatcher

How long serve_with_shutdown waits for connections to close after the tasks have ended. Default DEFAULT_DRAIN_TIMEOUT.

Source

pub async fn serve(self, addr: impl ToSocketAddrs) -> Result<(), Error>

Starts a gRPC server on the given address.

Blocks until the server shuts down. Uses the configured message size limits and concurrency settings.

It takes no shutdown signal; use serve_with_shutdown for a graceful stop.

§Errors

Returns std::io::Error if binding fails.

Source

pub async fn serve_with_addr( self, addr: impl ToSocketAddrs, ) -> Result<SocketAddr, Error>

Starts a gRPC server and returns the bound SocketAddr.

Like serve, but returns the address immediately and runs the server in a background task. Useful for tests.

§Errors

Returns std::io::Error if binding fails.

Source

pub fn serve_with_listener( self, listener: TcpListener, ) -> Result<SocketAddr, Error>

Starts a gRPC server on a pre-bound TcpListener.

This is the recommended approach when you need to know the server address before constructing the handler (e.g., for agent cards with correct URLs). Pre-bind the listener, extract the address, build your handler, then pass the listener here.

Returns the local address and runs the server in a background task.

§Errors

Returns std::io::Error if the listener’s local address cannot be read.

Source

pub fn into_service(&self) -> A2aServiceServer<A2aServiceImpl>

Builds the canonical tonic service for use with a custom server setup.

Returns an A2aServiceServer (the lf.a2a.v1.A2AService binding) that can be added to a tonic::transport::Server via add_service. It is the only gRPC service this dispatcher serves; the pre-0.7 JSON-tunnel companion was removed in 0.8.

Source§

impl GrpcDispatcher

Source

pub async fn serve_with_shutdown( self, listener: TcpListener, shutdown: impl Future<Output = ()> + Send, ) -> Result<ServeReport, Error>

Serves until shutdown resolves, then ends the work in flight and drains, the way Server::serve_with_shutdown does for the HTTP bindings:

  1. Stop accepting. The listener is closed, and every open connection is sent an HTTP/2 GOAWAY, so it finishes the calls it has and starts no new ones. That is one step earlier than the HTTP server, which keeps admitting requests on open connections through step 2; it is how tonic stops.
  2. End in-flight tasks with RequestHandler::finish_in_flight: up to with_completion_grace for them to finish on their own, then their cancellation tokens fire and executors get with_task_grace to act on it. A stream whose task ends gets its terminal event.
  3. Drain, for up to with_drain_timeout.

Call RequestHandler::shutdown after it returns. Dropping the returned future stops the server without any of this.

It takes a bound listener, as serve_with_listener does, so the caller knows the address — port 0 included — before serving starts.

§Errors

A TLS configuration tonic rejects, or the error the server stopped with. serve_with_listener discards the latter; this returns it.

Trait Implementations§

Source§

impl Debug for GrpcDispatcher

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self> ⓘ

Attaches the current Context to this type, returning a WithContext wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more