use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use crate::error::RuntimeError;
use super::state::RuntimeState;
pub(crate) struct ShutdownPlan {
pub(crate) readiness: Arc<AtomicBool>,
pub(crate) listener_cancel: CancellationToken,
pub(crate) health_cancel: CancellationToken,
pub(crate) connection_cancel: CancellationToken,
pub(crate) admin_cancel: CancellationToken,
pub(crate) state: Arc<RuntimeState>,
pub(crate) tasks: TaskTracker,
pub(crate) connection_tasks: TaskTracker,
pub(crate) admin_tasks: TaskTracker,
pub(crate) active_connections: Arc<AtomicU64>,
pub(crate) shutdown_grace: Duration,
#[cfg(feature = "ssh")]
pub(crate) ssh_sessions: Arc<eggress_transport_ssh::SshSessionCache>,
#[cfg(feature = "operations")]
pub(crate) compatibility_system_proxy: Option<eggress_system_proxy::AppliedProxy>,
}
pub(crate) async fn shutdown_ordered(plan: ShutdownPlan) -> Result<(), RuntimeError> {
plan.readiness.store(false, Ordering::Release);
plan.listener_cancel.cancel();
plan.health_cancel.cancel();
plan.state.udp_registry.close_all().await;
plan.state.udp_tasks.close();
let _ = tokio::time::timeout(plan.shutdown_grace, plan.state.udp_tasks.wait()).await;
plan.tasks.close();
plan.tasks.wait().await;
tracing::info!("draining active connections");
let deadline = tokio::time::Instant::now() + plan.shutdown_grace;
loop {
let active = plan.active_connections.load(Ordering::Acquire);
if active == 0 {
tracing::info!("all connections drained");
break;
}
if tokio::time::Instant::now() >= deadline {
tracing::warn!(active, "drain timeout reached, forcing shutdown");
plan.connection_cancel.cancel();
break;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
plan.connection_tasks.close();
plan.connection_tasks.wait().await;
#[cfg(feature = "ssh")]
plan.ssh_sessions.shutdown().await;
plan.admin_cancel.cancel();
plan.admin_tasks.close();
plan.admin_tasks.wait().await;
#[cfg(feature = "operations")]
if let Some(mut proxy) = plan.compatibility_system_proxy {
proxy.restore().map_err(RuntimeError::Other)?;
}
Ok(())
}