use std::future::Future;
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::task::{Context, Poll};
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
use super::GrpcDispatcher;
use super::bounded_incoming::{BoundedIncoming, OpenConnections, Permitted};
use crate::serve::ServeReport;
struct StopAccepting {
inner: Option<BoundedIncoming>,
stop: Pin<Box<WaitForCancellationFutureOwned>>,
accepted: Arc<AtomicU64>,
}
impl StopAccepting {
fn new(
listener: tokio::net::TcpListener,
max_connections: Option<usize>,
stop: &CancellationToken,
accepted: &Arc<AtomicU64>,
) -> (Self, OpenConnections) {
let (inner, open) = BoundedIncoming::counted(listener, max_connections);
let this = Self {
inner: Some(inner),
stop: Box::pin(stop.clone().cancelled_owned()),
accepted: Arc::clone(accepted),
};
(this, open)
}
}
impl tokio_stream::Stream for StopAccepting {
type Item = io::Result<Permitted>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = &mut *self;
if this.stop.as_mut().poll(cx).is_ready() {
this.inner = None;
return Poll::Ready(None);
}
let Some(inner) = this.inner.as_mut() else {
return Poll::Ready(None);
};
let next = Pin::new(inner).poll_next(cx);
if matches!(next, Poll::Ready(Some(Ok(_)))) {
this.accepted.fetch_add(1, Ordering::Relaxed);
}
next
}
}
fn stopped_early(ended: Result<(), tonic::transport::Error>) -> io::Error {
match ended {
Err(e) => io::Error::other(e),
Ok(()) => io::Error::other("the gRPC server stopped before shutdown was signalled"),
}
}
impl GrpcDispatcher {
pub async fn serve_with_shutdown(
self,
listener: tokio::net::TcpListener,
shutdown: impl Future<Output = ()> + Send,
) -> io::Result<ServeReport> {
trace_info!(
addr = ?listener.local_addr().ok(),
"A2A gRPC server listening (graceful)"
);
let accepted = Arc::new(AtomicU64::new(0));
let stop = CancellationToken::new();
let (incoming, open) = StopAccepting::new(listener, self.max_connections, &stop, &accepted);
let handler = Arc::clone(&self.handler);
let (completion, grace, drain) =
(self.completion_grace, self.task_grace, self.drain_timeout);
let serving = self
.build_router()?
.serve_with_incoming_shutdown(incoming, std::future::pending::<()>());
tokio::pin!(serving);
tokio::select! {
biased;
() = shutdown => {}
ended = &mut serving => return Err(stopped_early(ended)),
}
stop.cancel();
let (tasks, drained) =
finish_and_drain(serving, &handler, completion, grace, drain).await?;
Ok(ServeReport {
accepted: accepted.load(Ordering::Relaxed),
drained,
abandoned: if drained { 0 } else { open.count() },
tasks: Some(tasks),
})
}
}
async fn finish_and_drain(
mut serving: Pin<&mut impl Future<Output = Result<(), tonic::transport::Error>>>,
handler: &crate::RequestHandler,
completion: std::time::Duration,
grace: std::time::Duration,
drain: std::time::Duration,
) -> io::Result<(crate::handler::InFlightReport, bool)> {
let mut ended = None;
let tasks = {
let finish = handler.finish_in_flight(completion, grace);
tokio::pin!(finish);
tokio::select! {
tasks = &mut finish => tasks,
result = &mut serving => {
ended = Some(result);
finish.await
}
}
};
let ended = match ended {
Some(result) => Some(result),
None => tokio::time::timeout(drain, &mut serving).await.ok(),
};
match ended {
Some(result) => {
result.map_err(io::Error::other)?;
Ok((tasks, true))
}
None => Ok((tasks, false)),
}
}