use std::net::SocketAddr; use std::time::Duration; use tracing_subscriber::EnvFilter; /// Upper bound on how long we're willing to wait for the crawler daemon /// to drain before letting `main` return. Without it a wedged background /// task (e.g. a chromiumoxide handler stuck on a dead WS) blocks the /// process from exiting after Ctrl-C / SIGTERM. const CRAWLER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); #[tokio::main] async fn main() -> anyhow::Result<()> { dotenvy::dotenv().ok(); tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env().unwrap_or_else(|_| { "info,mangalord=debug,chromiumoxide::conn=off,chromiumoxide::handler=off".into() }), ) .init(); let config = mangalord::config::Config::from_env()?; let addr: SocketAddr = config.bind_address.parse()?; let mangalord::app::AppHandle { router, daemon } = mangalord::app::build(config).await?; tracing::info!(%addr, "mangalord listening"); let listener = tokio::net::TcpListener::bind(addr).await?; axum::serve(listener, router) .with_graceful_shutdown(shutdown_signal()) .await?; // Drain background tasks (crawler daemon) before exiting so Chromium // gets a clean shutdown rather than relying on kill-on-drop. Bounded // by a timeout so a wedged shutdown path can't trap the process. if let Some(d) = daemon { if tokio::time::timeout(CRAWLER_SHUTDOWN_TIMEOUT, d.shutdown()) .await .is_err() { tracing::warn!( timeout_s = CRAWLER_SHUTDOWN_TIMEOUT.as_secs(), "crawler daemon shutdown exceeded timeout; abandoning" ); } } Ok(()) } /// Wait for either Ctrl-C (interactive shell) or SIGTERM (Docker / /// Kubernetes / Podman / systemd stop) and log which arrived. Without /// the SIGTERM branch, `docker compose stop` runs out its grace period /// and skips straight to SIGKILL — the daemon never gets the /// `daemon.shutdown().await` path, leaking Chromium. async fn shutdown_signal() { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = match signal(SignalKind::terminate()) { Ok(s) => s, Err(e) => { // SignalKind::terminate() is supported on every Unix the // tokio runtime runs on; if registration fails we still // honour Ctrl-C so the process is at least // interactive-shutdownable. tracing::warn!(error = %e, "could not install SIGTERM handler; falling back to ctrl_c only"); let _ = tokio::signal::ctrl_c().await; tracing::info!("ctrl-c received; shutting down"); return; } }; tokio::select! { _ = tokio::signal::ctrl_c() => { tracing::info!("ctrl-c received; shutting down"); } _ = sigterm.recv() => { tracing::info!("SIGTERM received; shutting down"); } } }