use std::net::SocketAddr;
use std::path::Path;
use std::time::Duration;
use axum::Router;
use tokio::net::TcpListener;
use crate::config::Config;
use crate::error::AppError;
pub(crate) async fn run(config_path: &Path) -> Result<(), AppError> {
load_dotenv()?;
let config = Config::from_path(config_path)?;
crate::obs::init(&config.observability)?;
listen(config).await
}
pub(crate) async fn listen(config: Config) -> Result<(), AppError> {
let addr = config.server.bind;
let drain = Duration::from_secs(config.server.shutdown_timeout_secs);
let payment_enabled = config.payment.enabled;
let upstreams = config.upstreams.len();
let metrics_bind = config.observability.metrics_bind;
let (app, state) = crate::http::app_and_state(config)?;
let listener = bind(addr).await?;
let bound = listener.local_addr().unwrap_or(addr);
let metrics = crate::obs::metrics::bind(metrics_bind).await?;
let metrics_task = crate::obs::metrics::spawn(metrics);
tracing::info!(
%bound,
payment_enabled,
upstreams,
"listening"
);
let result = serve_until_drain(listener, app, drain).await;
state.inflight().wait_for_drain(drain).await;
if let Some(task) = metrics_task {
task.abort();
}
tracing::info!("shutdown complete");
result
}
fn load_dotenv() -> Result<(), AppError> {
match dotenvy::dotenv() {
Ok(_) => Ok(()),
Err(error) if error.not_found() => Ok(()),
Err(error) => Err(AppError::Dotenv { source: error }),
}
}
async fn bind(addr: SocketAddr) -> Result<TcpListener, AppError> {
TcpListener::bind(addr)
.await
.map_err(|source| AppError::Bind { addr, source })
}
async fn serve_until_drain(
listener: TcpListener,
app: Router,
drain: Duration,
) -> Result<(), AppError> {
tokio::select! {
result = axum::serve(listener, app).with_graceful_shutdown(crate::shutdown::signal()) => {
result.map_err(|source| AppError::Server { source })
}
() = drain_deadline(drain) => {
tracing::warn!(
timeout_secs = drain.as_secs(),
"shutdown timeout elapsed; dropping remaining connections"
);
Ok(())
}
}
}
async fn drain_deadline(drain: Duration) {
crate::shutdown::signal().await;
tokio::time::sleep(drain).await;
}