use std::time::Duration;
use anyhow::Result;
use arcbox_docker::DockerContextManager;
use tokio::signal;
use tracing::{info, warn};
use crate::context::{DaemonContext, ServiceHandles};
const DRAIN_TIMEOUT: Duration = Duration::from_secs(5);
pub async fn run(ctx: DaemonContext, mut handles: ServiceHandles) -> Result<()> {
wait_for_signal().await;
println!("Shutting down guest VM... (press Ctrl+C again to force quit)");
info!("Shutdown signal received, draining connections...");
ctx.shutdown.cancel();
drain(&mut handles).await;
let forced = if let Some(runtime) = ctx.shared_runtime.get() {
let runtime = runtime.clone();
let shutdown_task = tokio::spawn(async move { runtime.shutdown().await });
tokio::select! {
result = shutdown_task => {
match result {
Ok(Err(e)) => warn!("Runtime shutdown error: {e}"),
Err(e) => warn!("Runtime shutdown task panicked: {e}"),
_ => {}
}
false
}
() = wait_for_signal() => {
println!("Force shutting down...");
warn!("Second signal received, forcing shutdown");
true
}
}
} else {
false
};
if forced {
if let Some(runtime) = ctx.shared_runtime.get() {
let _ = runtime.shutdown_force().await;
}
}
cleanup(&ctx).await;
info!("ArcBox daemon stopped");
if forced {
std::process::exit(0);
}
Ok(())
}
async fn cleanup(ctx: &DaemonContext) {
#[cfg(target_os = "macos")]
{
arcbox_core::route_reconciler::remove_route().await;
}
if ctx.docker_integration {
if let Ok(ctx_manager) = DockerContextManager::new(ctx.layout.docker_socket.clone()) {
let _ = ctx_manager.disable();
}
}
remove_sockets(ctx);
}
fn remove_sockets(ctx: &DaemonContext) {
for path in [&ctx.layout.docker_socket, &ctx.layout.grpc_socket] {
if let Err(e) = std::fs::remove_file(path) {
if e.kind() != std::io::ErrorKind::NotFound {
warn!("Failed to remove socket {}: {}", path.display(), e);
}
}
}
}
async fn wait_for_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
}
async fn drain(handles: &mut ServiceHandles) {
if tokio::time::timeout(DRAIN_TIMEOUT, async {
let _ = tokio::join!(&mut handles.dns, &mut handles.docker, &mut handles.grpc);
if let Some(h) = handles.kubernetes_proxy.as_mut() {
let _ = h.await;
}
})
.await
.is_err()
{
warn!(
"Server drain timed out after {}s, aborting remaining tasks",
DRAIN_TIMEOUT.as_secs()
);
handles.dns.abort();
handles.docker.abort();
handles.grpc.abort();
if let Some(h) = handles.kubernetes_proxy.as_mut() {
h.abort();
}
}
}