use std::convert::Infallible;
use std::future::Future;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpListener;
use tokio::sync::Semaphore;
use super::{pause_after_accept_error, Dispatcher};
mod idle;
use idle::IdleTimeout;
pub const DEFAULT_DRAIN_TIMEOUT: Duration = Duration::from_secs(15);
pub const DEFAULT_HEADER_READ_TIMEOUT: Duration = Duration::from_secs(30);
pub const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(75);
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ServeConfig {
pub max_connections: Option<usize>,
pub drain_timeout: Duration,
pub header_read_timeout: Option<Duration>,
pub idle_timeout: Option<Duration>,
}
impl Default for ServeConfig {
fn default() -> Self {
Self {
max_connections: None,
drain_timeout: DEFAULT_DRAIN_TIMEOUT,
header_read_timeout: Some(DEFAULT_HEADER_READ_TIMEOUT),
idle_timeout: Some(DEFAULT_IDLE_TIMEOUT),
}
}
}
impl ServeConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_max_connections(mut self, max: usize) -> Self {
self.max_connections = Some(max);
self
}
#[must_use]
pub const fn with_drain_timeout(mut self, timeout: Duration) -> Self {
self.drain_timeout = timeout;
self
}
#[must_use]
pub const fn with_header_read_timeout(mut self, timeout: Option<Duration>) -> Self {
self.header_read_timeout = timeout;
self
}
#[must_use]
pub const fn with_idle_timeout(mut self, timeout: Option<Duration>) -> Self {
self.idle_timeout = timeout;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ServeReport {
pub accepted: u64,
pub drained: bool,
pub abandoned: usize,
}
#[derive(Debug)]
pub struct Server {
listener: TcpListener,
config: ServeConfig,
}
impl Server {
pub async fn bind(addr: impl tokio::net::ToSocketAddrs) -> std::io::Result<Self> {
Ok(Self {
listener: TcpListener::bind(addr).await?,
config: ServeConfig::default(),
})
}
#[must_use]
pub const fn with_config(mut self, config: ServeConfig) -> Self {
self.config = config;
self
}
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
self.listener.local_addr()
}
pub async fn serve_with_shutdown(
self,
dispatcher: impl Dispatcher,
shutdown: impl Future<Output = ()> + Send,
) -> ServeReport {
let Self { listener, config } = self;
let dispatcher = Arc::new(dispatcher);
let graceful = hyper_util::server::graceful::GracefulShutdown::new();
let accepted = AtomicU64::new(0);
let permits = Arc::new(Semaphore::new(
config.max_connections.unwrap_or(Semaphore::MAX_PERMITS),
));
trace_info!(
addr = %listener.local_addr().unwrap_or_else(|_| SocketAddr::from(([0, 0, 0, 0], 0))),
max_connections = ?config.max_connections,
"A2A server listening (graceful)"
);
let mut shutdown = std::pin::pin!(shutdown);
loop {
let Ok(permit) = Arc::clone(&permits).acquire_owned().await else {
break;
};
let accept = tokio::select! {
biased;
() = &mut shutdown => break,
accept = listener.accept() => accept,
};
let (stream, _peer) = match accept {
Ok(pair) => pair,
Err(e) => {
trace_warn!(error = %e, "accept() failed; retrying");
pause_after_accept_error(&e).await;
continue;
}
};
accepted.fetch_add(1, Ordering::Relaxed);
spawn_connection(
stream,
Arc::clone(&dispatcher),
graceful.watcher(),
permit,
&config,
);
}
drain(
graceful,
accepted.load(Ordering::Relaxed),
config.drain_timeout,
)
.await
}
}
fn spawn_connection(
stream: tokio::net::TcpStream,
dispatcher: Arc<impl Dispatcher>,
watcher: hyper_util::server::graceful::Watcher,
permit: tokio::sync::OwnedSemaphorePermit,
config: &ServeConfig,
) {
let _ = stream.set_nodelay(true);
let io = hyper_util::rt::TokioIo::new(IdleTimeout::new(stream, config.idle_timeout));
let header_read_timeout = config.header_read_timeout;
tokio::spawn(async move {
let service = hyper::service::service_fn(move |req| {
let d = Arc::clone(&dispatcher);
async move { Ok::<_, Infallible>(d.dispatch(req).await) }
});
let mut builder =
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
builder
.http1()
.timer(hyper_util::rt::TokioTimer::new())
.header_read_timeout(header_read_timeout);
builder.http2().timer(hyper_util::rt::TokioTimer::new());
let conn = builder.serve_connection(io, service);
if let Err(_e) = watcher.watch(conn).await {
trace_warn!(error = %_e, "connection error");
}
drop(permit);
});
}
async fn drain(
graceful: hyper_util::server::graceful::GracefulShutdown,
accepted: u64,
timeout: Duration,
) -> ServeReport {
let in_flight = graceful.count();
trace_info!(
accepted,
in_flight,
"shutdown signalled; draining connections"
);
if tokio::time::timeout(timeout, graceful.shutdown())
.await
.is_ok()
{
ServeReport {
accepted,
drained: true,
abandoned: 0,
}
} else {
trace_warn!(
abandoned = in_flight,
"drain timeout expired with connections still open"
);
ServeReport {
accepted,
drained: false,
abandoned: in_flight,
}
}
}
#[cfg(test)]
mod tests;