#[cfg(unix)]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
#[cfg(windows)]
#[global_allocator]
static GLOBAL_WIN: mimalloc::MiMalloc = mimalloc::MiMalloc;
use clap::Parser;
use zccache::core::NormalizedPath;
fn daemon_max_blocking_threads() -> usize {
let parallelism = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(8);
parallelism.saturating_mul(8).clamp(128, 512)
}
const DAEMON_PROFILE_ENV: &str = "ZCCACHE_DAEMON_PROFILE";
const TOKIO_CONSOLE_PROFILE: &str = "tokio-console";
const TOKIO_CONSOLE_BIND_ENV: &str = "TOKIO_CONSOLE_BIND";
const TOKIO_CONSOLE_DEFAULT_BIND: &str = "127.0.0.1:6669";
#[derive(Debug, Parser)]
#[command(name = "zccache-daemon", version, about)]
struct Args {
#[arg(long)]
config: Option<NormalizedPath>,
#[arg(long, default_value = "info")]
log_level: String,
#[arg(long)]
foreground: bool,
#[arg(long)]
endpoint: Option<String>,
#[arg(
long,
default_value_t = zccache::core::config::DEFAULT_IDLE_TIMEOUT_SECS,
env = "ZCCACHE_IDLE_TIMEOUT_SECS"
)]
idle_timeout: u64,
#[arg(long)]
no_depgraph_cache: bool,
#[arg(long)]
log_file: Option<NormalizedPath>,
}
fn main() {
let args = Args::parse();
if args.foreground {
match args.log_file.as_deref() {
Some(path) => zccache::daemon::trampoline::redirect_stdio_to_log(path),
None => zccache::daemon::trampoline::detach_stdio(),
}
init_tracing(&args.log_level);
zccache::daemon::trampoline::unlock_exe();
zccache::daemon::trampoline::release_cwd();
run_server(args);
} else {
print_status(&args);
}
}
fn print_status(args: &Args) {
let endpoint = args
.endpoint
.clone()
.unwrap_or_else(zccache::ipc::default_endpoint);
println!("zccache-daemon v{}", env!("CARGO_PKG_VERSION"));
println!();
println!(" endpoint: {endpoint}");
println!(
" namespace: {}",
zccache::core::config::daemon_namespace_label()
);
println!(" lock file: {}", zccache::ipc::lock_file_path().display());
println!();
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to create tokio runtime");
match rt.block_on(query_daemon_status(&endpoint)) {
Ok(status) => {
println!(" status: running");
println!(" daemon ns: {}", status.daemon_namespace);
println!(" daemon ep: {}", status.endpoint);
println!(
" private: {}",
if status.private_daemon.enabled {
"yes"
} else {
"no"
}
);
println!(" uptime: {}s", status.uptime_secs);
println!(" artifacts: {}", status.artifact_count);
println!(" cache size: {} bytes", status.cache_size_bytes);
println!(" metadata: {} entries", status.metadata_entries);
println!(
" hits/miss: {} / {}",
status.cache_hits, status.cache_misses
);
}
Err(_) => {
println!(" status: not running");
println!();
println!("Start with: zccache-daemon --foreground");
}
}
}
async fn query_daemon_status(
endpoint: &str,
) -> Result<zccache::protocol::DaemonStatus, Box<dyn std::error::Error>> {
let resp = zccache::ipc::daemon_control_roundtrip(
endpoint,
zccache::ipc::DaemonControlRequest::Status,
None,
)
.await?;
match resp {
Some(zccache::protocol::Response::Status(s)) => Ok(s),
Some(other) => Err(format!("unexpected response: {other:?}").into()),
None => Err("connection closed".into()),
}
}
fn run_server(args: Args) {
let endpoint = args.endpoint.unwrap_or_else(zccache::ipc::default_endpoint);
let idle_timeout = args.idle_timeout;
let _crash_guard = zccache::core::crash::install("zccache-daemon");
zccache::core::crash::check_previous_crashes();
tracing::info!(%endpoint, idle_timeout, "zccache-daemon starting");
{
let probe_rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to create probe runtime");
let existing_daemon = probe_rt.block_on(zccache::ipc::probe_existing_daemon(
&endpoint,
std::time::Duration::from_millis(500),
));
if existing_daemon {
tracing::info!(
%endpoint,
"active daemon already serving — deferring"
);
std::process::exit(0);
}
}
let cache_root = zccache::core::config::default_cache_dir();
zccache::core::defender::maybe_emit_first_run_banner(cache_root.as_path());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.max_blocking_threads(daemon_max_blocking_threads())
.build()
.expect("failed to create tokio runtime");
let no_depgraph_cache = args.no_depgraph_cache;
let manifest_cache_root = cache_root.clone();
rt.block_on(async move {
let bind_endpoint = endpoint.clone();
let bind_result = tokio::task::spawn_blocking(move || {
zccache::daemon::DaemonServer::bind(&bind_endpoint)
})
.await;
let server = match bind_result {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
let is_pipe_in_use = matches!(
&e,
zccache::ipc::IpcError::Io(io_err) if matches!(
io_err.kind(),
std::io::ErrorKind::PermissionDenied
| std::io::ErrorKind::AddrInUse
| std::io::ErrorKind::AlreadyExists
)
);
if is_pipe_in_use {
tracing::info!(
%endpoint,
error = %e,
"another daemon already owns endpoint — deferring"
);
std::process::exit(0);
}
tracing::error!("failed to bind {endpoint}: {e}");
zccache::ipc::remove_lock_file();
std::process::exit(1);
}
Err(e) => {
tracing::error!("failed to join daemon bind worker for {endpoint}: {e}");
zccache::ipc::remove_lock_file();
std::process::exit(1);
}
};
let spawn_meta = zccache::daemon::lifecycle::client_meta(env!("CARGO_PKG_VERSION"));
zccache::daemon::lifecycle::write_event(
zccache::daemon::lifecycle::EVENT_SPAWN,
serde_json::json!({
"endpoint": &endpoint,
"daemon_namespace": zccache::core::config::daemon_namespace_label(),
"idle_timeout": idle_timeout,
"version": env!("CARGO_PKG_VERSION"),
"client_version": spawn_meta["client_version"],
"client_binary_path": spawn_meta["client_binary_path"],
}),
);
let pid = std::process::id();
if let Err(e) = zccache::ipc::write_lock_file(pid) {
tracing::warn!("failed to write lock file: {e}");
}
if let Err(e) = zccache::ipc::write_backend_identity(&server.backend_identity()) {
tracing::warn!("failed to write running-process backend identity: {e}");
}
if let Some(path) = zccache::ipc::publish_manifest(&manifest_cache_root) {
tracing::debug!(manifest = %path.display(), "published running-process cache manifest");
}
if let Ok(binary) = std::env::current_exe() {
let _ = zccache::ipc::publish_service_definition(&binary);
}
server.mark_dep_graph_load_pending();
let setter = server.dep_graph_setter();
let depgraph_path = zccache::depgraph::depgraph_file_path();
let load_handle = tokio::task::spawn_blocking(move || {
if no_depgraph_cache {
let _ = std::fs::remove_file(&depgraph_path);
tracing::info!("depgraph cache disabled — starting with empty graph");
setter.install(None, None);
return;
}
let start = std::time::Instant::now();
let outcome = zccache::depgraph::classify_load(&depgraph_path);
let warning = outcome.warning(&depgraph_path);
match outcome {
zccache::depgraph::DepGraphLoadOutcome::Loaded { graph } => {
let stats = graph.stats();
let (cold_ctxs, warm_ctxs, stale_ctxs) = graph.state_breakdown();
let ctxs_with_key = graph.contexts_with_artifact_key();
tracing::info!(
contexts = stats.context_count,
files = stats.file_count,
cold = cold_ctxs,
warm = warm_ctxs,
stale = stale_ctxs,
with_artifact_key = ctxs_with_key,
elapsed_ms = start.elapsed().as_millis() as u64,
"loaded depgraph from disk (background)"
);
setter.install(Some(graph), None);
}
zccache::depgraph::DepGraphLoadOutcome::Missing => {
setter.install(None, None);
}
zccache::depgraph::DepGraphLoadOutcome::VersionMismatch {
file_version,
expected_version,
} => {
tracing::warn!(
file_version,
expected_version,
"depgraph version mismatch — starting with empty graph"
);
if let Some(ref w) = warning {
eprintln!("{w}");
}
setter.install(None, warning);
}
zccache::depgraph::DepGraphLoadOutcome::Corrupt { ref message }
| zccache::depgraph::DepGraphLoadOutcome::IoError { ref message } => {
tracing::warn!("depgraph load failed: {message} — starting with empty graph");
if let Some(ref w) = warning {
eprintln!("{w}");
}
setter.install(None, warning);
}
}
});
let compiler_hash_loader = server.compiler_hash_cache_loader();
tokio::task::spawn_blocking(move || {
compiler_hash_loader.load_and_install();
});
let metadata_loader = server.metadata_cache_loader();
tokio::task::spawn_blocking(move || {
metadata_loader.load_and_install();
});
let system_includes_loader = server.system_includes_loader();
tokio::task::spawn_blocking(move || {
system_includes_loader.load_and_install();
});
let artifact_store_loader = server.artifact_store_loader();
tokio::task::spawn_blocking(move || {
artifact_store_loader.load_and_install();
});
let shutdown = server.shutdown_handle();
tokio::spawn(async move {
if let Ok(()) = tokio::signal::ctrl_c().await {
tracing::info!("received Ctrl+C — shutting down");
shutdown.notify_one();
}
});
tracing::info!(%endpoint, "listening for connections");
let mut server = server;
if let Err(e) = server.run(idle_timeout).await {
tracing::error!("server error: {e}");
zccache::ipc::remove_lock_file();
load_handle.abort();
std::process::exit(1);
}
tracing::info!("daemon exiting cleanly");
zccache::ipc::remove_lock_file();
load_handle.abort();
});
}
fn init_tracing(level: &str) {
use tracing_subscriber::{prelude::*, EnvFilter};
let mut filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level));
for target in [
"zccache_artifact",
"zccache_compiler",
"zccache_core",
"zccache_depgraph",
"zccache_download",
"zccache_fingerprint",
"zccache_fscache",
"zccache_gha",
"zccache_hash",
"zccache_ipc",
"zccache_protocol",
"zccache_symbols",
"zccache_watcher",
] {
if let Ok(d) = format!("{target}=info").parse() {
filter = filter.add_directive(d);
}
}
let profile = std::env::var(DAEMON_PROFILE_ENV).ok();
let tokio_console_enabled = profile.as_deref() == Some(TOKIO_CONSOLE_PROFILE);
if tokio_console_enabled {
let bind = std::env::var(TOKIO_CONSOLE_BIND_ENV)
.unwrap_or_else(|_| TOKIO_CONSOLE_DEFAULT_BIND.to_string());
let console_layer = std::panic::catch_unwind(console_subscriber::spawn);
match console_layer {
Ok(console_layer) => {
tracing_subscriber::registry()
.with(console_layer)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_filter(filter),
)
.init();
tracing::info!(
bind,
"tokio-console daemon profile enabled; connect with `tokio-console {bind}`"
);
}
Err(_) => {
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_filter(filter),
)
.init();
tracing::warn!(
bind,
"tokio-console daemon profile requested but unavailable; \
rebuild with RUSTFLAGS=\"--cfg tokio_unstable\""
);
}
}
return;
}
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_thread_ids(true)
.with_filter(filter),
)
.init();
if let Some(profile) = profile {
if profile != TOKIO_CONSOLE_PROFILE {
tracing::warn!(
profile,
"unknown daemon profile requested; running without extra profiling"
);
}
}
}