use super::*;
pub(super) static SERVER_INSTANCE: AtomicU64 = AtomicU64::new(0);
pub(super) static ARTIFACT_PERSIST_TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
impl DaemonServer {
pub fn bind(endpoint: &str) -> Result<Self, crate::ipc::IpcError> {
Self::bind_with_cache_dir(endpoint, &crate::core::config::default_cache_dir())
}
pub fn bind_with_cache_dir(
endpoint: &str,
cache_dir: &crate::core::NormalizedPath,
) -> Result<Self, crate::ipc::IpcError> {
let listener = IpcListener::bind(endpoint)?;
let backend_identity = crate::ipc::current_backend_identity(endpoint)
.map_err(|err| crate::ipc::IpcError::Endpoint(err.to_string()))?;
let shutdown = Arc::new(Notify::new());
let now = now_secs();
let instance = SERVER_INSTANCE.fetch_add(1, Ordering::Relaxed);
let artifact_dir = crate::core::config::artifacts_dir_from_cache_dir(cache_dir);
std::fs::create_dir_all(&artifact_dir).ok();
let artifacts: DashMap<String, CachedArtifact> = DashMap::new();
let index_path = crate::core::config::index_path_from_cache_dir(cache_dir);
let artifact_store = Arc::new(ArtifactStore::open_empty(&index_path));
let (index_writer_tx, index_writer_rx) =
tokio::sync::mpsc::unbounded_channel::<(String, ArtifactIndex)>();
let index_writer_shutdown = Arc::new(Notify::new());
let metadata_path = crate::core::config::metadata_path_from_cache_dir(cache_dir);
let compiler_hash_cache_path =
crate::core::config::compiler_hash_cache_path_from_cache_dir(cache_dir);
let system_includes_cache_path =
crate::core::config::system_includes_cache_path_from_cache_dir(cache_dir);
let system_includes_loaded = crate::depgraph::SystemIncludeCache::new();
let compiler_hash_cache = CompilerHashCache::new();
let cache_system = CacheSystem::new();
let ci_env = crate::daemon::process::is_ci_host();
match ci_env {
Some(env_var) => tracing::info!(
ci_env = env_var,
"[zccache] CI detected via {env_var} — compile/link priority defaults to Normal \
(set ZCCACHE_COMPILE_PRIORITY to override)",
),
None => tracing::info!(
"[zccache] interactive host — compile/link priority defaults to Low \
(set ZCCACHE_COMPILE_PRIORITY to override)",
),
}
let compile_concurrency =
crate::daemon::server::compile_concurrency::resolve_pool(ci_env.is_some());
match &compile_concurrency {
Some(sem) => tracing::info!(
cap = sem.available_permits(),
"[zccache] compile concurrency capped at {} via in-process semaphore \
(set ZCCACHE_MAX_PARALLEL_COMPILES to override; =0 to disable)",
sem.available_permits()
),
None => tracing::info!(
"[zccache] compile concurrency uncapped (ZCCACHE_MAX_PARALLEL_COMPILES=0)",
),
}
Ok(Self {
listener,
shutdown: Arc::clone(&shutdown),
index_writer_rx: Some(index_writer_rx),
state: Arc::new(SharedState {
endpoint: endpoint.to_string(),
backend_identity,
daemon_namespace: crate::core::config::daemon_namespace_label(),
cache_dir: cache_dir.clone(),
private_daemon: PrivateDaemonLifecycle::new(),
sessions: SessionManager::new(std::time::Duration::from_secs(300)),
system_includes: Mutex::new(system_includes_loaded),
system_includes_cache_path,
dep_graph: arc_swap::ArcSwap::from_pointee(DepGraph::new()),
artifacts,
cache_system,
watcher: Mutex::new(None),
watched_dirs: Mutex::new(HashSet::new()),
shutdown,
last_activity: AtomicU64::new(now),
start_time: now,
stats: StatsCollector::new(),
profiler: PhaseProfiler::new(),
artifact_dir,
metadata_path,
compiler_hash_cache_path,
depfile_tmpdir: {
let dir = crate::core::config::depfile_dir_from_cache_dir(cache_dir)
.join(format!("{}-{instance}", std::process::id()));
std::fs::create_dir_all(&dir).ok();
dir
},
fast_hit_cache: DashMap::new(),
watcher_active: AtomicBool::new(false),
rsp_cache: DashMap::new(),
request_cache: DashMap::new(),
session_worktree_roots: DashMap::new(),
request_validation_cache: DashMap::new(),
compiler_hash_cache,
watched_raw_dirs: DashMap::new(),
pch_source_map: DashMap::new(),
journal: CompileJournal::new(crate::core::config::log_dir_from_cache_dir(
cache_dir,
)),
in_flight_bytes: AtomicUsize::new(0),
persist_semaphore: Arc::new(tokio::sync::Semaphore::new(persist_workers_default())),
compile_concurrency,
artifact_store,
index_writer_tx,
index_writer_shutdown,
artifacts_loaded: AtomicBool::new(false),
compiler_hash_cache_loaded: AtomicBool::new(false),
metadata_cache_loaded: AtomicBool::new(false),
system_includes_loaded: AtomicBool::new(false),
artifact_store_loaded: AtomicBool::new(false),
shutdown_event_logged: AtomicBool::new(false),
shutdown_requested: AtomicBool::new(false),
fingerprint: FingerprintManager::new(),
dep_graph_persisted: AtomicBool::new(false),
dep_graph_load_complete: AtomicBool::new(true),
dep_graph_load_notify: Arc::new(Notify::new()),
depgraph_load_warning: Mutex::new(None),
in_flight_exec: DashMap::new(),
pending_cache_writes: DashMap::new(),
exec_cache: DashMap::new(),
}),
})
}
#[must_use]
pub fn shutdown_handle(&self) -> Arc<Notify> {
Arc::clone(&self.shutdown)
}
#[must_use]
pub fn backend_identity(
&self,
) -> running_process::broker::protocol_v2::backend_handle::DaemonProcess {
self.state.backend_identity.clone()
}
pub fn set_dep_graph(&self, graph: crate::depgraph::DepGraph) {
self.state.dep_graph.store(std::sync::Arc::new(graph));
self.state
.dep_graph_persisted
.store(true, Ordering::Release);
self.state
.dep_graph_load_complete
.store(true, Ordering::Release);
self.state.dep_graph_load_notify.notify_waiters();
}
#[doc(hidden)]
pub fn mark_dep_graph_load_pending(&self) {
self.state
.dep_graph_load_complete
.store(false, Ordering::Release);
}
pub fn set_depgraph_load_warning(&self, warning: String) {
let mut guard = self.state.depgraph_load_warning.blocking_lock();
*guard = Some(warning);
}
pub fn dep_graph_setter(&self) -> DepGraphSetter {
DepGraphSetter {
state: Arc::clone(&self.state),
}
}
pub fn compiler_hash_cache_loader(&self) -> CompilerHashCacheLoader {
CompilerHashCacheLoader {
state: Arc::clone(&self.state),
path: self.state.compiler_hash_cache_path.clone(),
}
}
pub fn metadata_cache_loader(&self) -> MetadataCacheLoader {
MetadataCacheLoader {
state: Arc::clone(&self.state),
path: self.state.metadata_path.clone(),
}
}
pub fn system_includes_loader(&self) -> SystemIncludesLoader {
SystemIncludesLoader {
state: Arc::clone(&self.state),
path: self.state.system_includes_cache_path.clone(),
}
}
pub fn artifact_store_loader(&self) -> ArtifactStoreLoader {
ArtifactStoreLoader {
state: Arc::clone(&self.state),
store: Arc::clone(&self.state.artifact_store),
}
}
#[must_use]
pub fn profile_snapshot(&self) -> super::super::stats::ProfileSnapshot {
self.state.profiler.snapshot()
}
#[doc(hidden)]
#[must_use]
pub fn test_lookup_artifact(&self, key_hex: &str) -> bool {
lookup_artifact_with_disk_fallback(&self.state, key_hex).is_some()
}
#[doc(hidden)]
#[must_use]
pub fn test_artifacts_loaded(&self) -> bool {
self.state.artifacts_loaded.load(Ordering::Acquire)
}
#[doc(hidden)]
#[must_use]
pub fn test_artifacts_len(&self) -> usize {
self.state.artifacts.len()
}
#[doc(hidden)]
pub async fn test_insert_system_includes(
&self,
compiler: crate::core::NormalizedPath,
paths: Vec<crate::core::NormalizedPath>,
) {
let mut cache = self.state.system_includes.lock().await;
cache.insert(compiler, paths);
}
#[doc(hidden)]
#[must_use]
pub async fn test_system_includes_len(&self) -> usize {
self.state.system_includes.lock().await.len()
}
#[doc(hidden)]
#[cfg(test)]
#[must_use]
pub(super) fn test_state(&self) -> &SharedState {
&self.state
}
#[doc(hidden)]
#[cfg(test)]
#[must_use]
pub(super) fn test_state_arc(&self) -> Arc<SharedState> {
Arc::clone(&self.state)
}
}
pub struct DepGraphSetter {
state: Arc<SharedState>,
}
impl DepGraphSetter {
pub fn install(self, graph: Option<crate::depgraph::DepGraph>, warning: Option<String>) {
if let Some(graph) = graph {
self.state.dep_graph.store(Arc::new(graph));
self.state
.dep_graph_persisted
.store(true, Ordering::Release);
}
if let Some(warning) = warning {
let mut guard = self.state.depgraph_load_warning.blocking_lock();
*guard = Some(warning);
}
self.state
.dep_graph_load_complete
.store(true, Ordering::Release);
self.state.dep_graph_load_notify.notify_waiters();
}
}
pub struct CompilerHashCacheLoader {
state: Arc<SharedState>,
path: crate::core::NormalizedPath,
}
pub struct SystemIncludesLoader {
state: Arc<SharedState>,
path: crate::core::NormalizedPath,
}
pub struct ArtifactStoreLoader {
state: Arc<SharedState>,
store: Arc<ArtifactStore>,
}
impl ArtifactStoreLoader {
pub fn load_and_install(self) {
if let Err(e) = self.store.load_from_disk() {
tracing::warn!("artifact index load failed, continuing with empty store: {e}");
}
self.state
.artifact_store_loaded
.store(true, Ordering::Release);
}
}
impl SystemIncludesLoader {
pub fn load_and_install(self) {
match crate::depgraph::SystemIncludeCache::load_from_disk(self.path.as_path()) {
Ok(loaded) => {
let loaded_len = loaded.len();
{
let mut live = self.state.system_includes.blocking_lock();
live.merge_from(loaded);
}
if loaded_len > 0 {
tracing::info!(
loaded = loaded_len,
path = %self.path.display(),
"system include cache restored from disk (background)"
);
}
}
Err(e) => {
tracing::warn!(
path = %self.path.display(),
"failed to load system include cache, starting empty: {e}"
);
}
}
self.state
.system_includes_loaded
.store(true, Ordering::Release);
}
}
impl CompilerHashCacheLoader {
pub fn load_and_install(self) {
match CompilerHashCache::load_from_disk(self.path.as_path()) {
Ok(loaded) => {
self.state.compiler_hash_cache.merge_from(loaded);
}
Err(e) => {
tracing::warn!(
path = %self.path.display(),
"failed to load compiler hash cache, starting empty: {e}"
);
}
}
self.state
.compiler_hash_cache_loaded
.store(true, Ordering::Release);
}
}
pub struct MetadataCacheLoader {
state: Arc<SharedState>,
path: crate::core::NormalizedPath,
}
impl MetadataCacheLoader {
pub fn load_and_install(self) {
match crate::fscache::MetadataCache::load_from_disk(self.path.as_path()) {
Ok(loaded) => {
let loaded_len = loaded.len();
self.state.cache_system.metadata().merge_from(loaded);
if loaded_len > 0 {
tracing::info!(
loaded = loaded_len,
path = %self.path.display(),
"metadata cache restored from disk (background)"
);
}
}
Err(e) => {
tracing::warn!(
path = %self.path.display(),
"failed to load metadata cache, starting empty: {e}"
);
}
}
self.state
.metadata_cache_loaded
.store(true, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ctx(source: &str) -> crate::depgraph::CompileContext {
crate::depgraph::CompileContext {
source_file: source.into(),
include_search: crate::depgraph::IncludeSearchPaths::default(),
defines: Vec::new(),
flags: Vec::new(),
force_includes: Vec::new(),
unknown_flags: Vec::new(),
}
}
fn dummy_hash(path: &std::path::Path) -> Option<crate::hash::ContentHash> {
Some(crate::hash::hash_bytes(path.to_string_lossy().as_bytes()))
}
#[tokio::test]
async fn depgraph_load_gate_waits_until_loaded_graph_is_visible() {
let server = DaemonServer::bind(&crate::ipc::unique_test_endpoint()).unwrap();
let state = Arc::clone(&server.state);
let setter = server.dep_graph_setter();
let graph = crate::depgraph::DepGraph::new();
let ctx = make_ctx("/src/warm.cc");
let key = graph.register(ctx);
graph.update(
&key,
crate::depgraph::ScanResult {
resolved: Vec::new(),
unresolved: Vec::new(),
has_computed: false,
},
dummy_hash,
);
server.mark_dep_graph_load_pending();
let handle = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(25));
setter.install(Some(graph), None);
});
tokio::time::timeout(
std::time::Duration::from_secs(2),
state.dep_graph_load_notify.notified(),
)
.await
.expect("depgraph load notify should fire");
handle.join().unwrap();
assert!(
!state.dep_graph.load().is_cold(&key),
"first compile must see the loaded warm graph instead of the empty default"
);
assert!(state.dep_graph_persisted.load(Ordering::Acquire));
}
}