1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! `SharedState` — the daemon's central state object shared by every
//! request handler.
//!
//! Every connection handler receives an `Arc<SharedState>` and reads from
//! these fields directly. Most fields are append-only after `DaemonServer::bind`
//! (`sessions`, `journal`, `artifact_store`); the lock-free `DashMap`s are
//! contended by request handlers concurrently.
use super::*;
/// Shared state accessible by all connection handlers.
pub(super) struct SharedState {
/// IPC endpoint this daemon bound. Reported through `zccache status` so
/// wrappers can verify they reached the intended daemon identity.
pub(super) endpoint: String,
/// Active daemon/socket namespace label.
pub(super) daemon_namespace: String,
/// Cache root this daemon was created with.
pub(super) cache_dir: NormalizedPath,
/// Private daemon lifetime/ref-count state.
pub(super) private_daemon: PrivateDaemonLifecycle,
pub(super) sessions: SessionManager,
pub(super) system_includes: Mutex<SystemIncludeCache>,
/// Dependency graph: tracks include relationships and cache verdicts.
///
/// **Wrapped in `ArcSwap` per #640** so that the on-disk-loaded graph
/// can be installed *after* `DaemonServer::bind` has handed out
/// `Arc<SharedState>` clones to spawned tasks — a constraint the prior
/// `Arc::get_mut`-based `set_dep_graph` could not satisfy. The initial
/// value is `Arc::new(DepGraph::default())`; the first
/// [`DaemonServer::set_dep_graph`] call atomically swaps in the loaded
/// graph. Subsequent calls also swap (no one-shot constraint).
///
/// All reader access is `state.dep_graph.load().method(...)`. The
/// `Guard<Arc<DepGraph>>` returned by `.load()` derefs to `&DepGraph`,
/// so existing method calls work unchanged once the `.load()` is
/// inserted. Cache that guard in a local when multiple methods on the
/// same graph snapshot are needed in one logical operation, so a
/// concurrent swap can't split the operation across two graph
/// generations.
pub(super) dep_graph: arc_swap::ArcSwap<crate::depgraph::DepGraph>,
/// In-memory artifact cache: artifact_key_hex → artifact data.
pub(super) artifacts: DashMap<String, CachedArtifact>,
/// Metadata cache + change journal. The watcher feeds file-change events
/// into this, which downgrades confidence so `lookup()` re-hashes on
/// next access. Without the watcher, stat-verify on every `lookup()` is
/// the fallback (correct but slower).
pub(super) cache_system: CacheSystem,
/// File watcher for proactive metadata invalidation.
pub(super) watcher: Mutex<Option<NotifyWatcher>>,
/// Directories currently being watched (avoid duplicate watches).
pub(super) watched_dirs: Mutex<HashSet<NormalizedPath>>,
/// Shutdown signal — shared so request handlers can trigger shutdown.
pub(super) shutdown: Arc<Notify>,
/// Epoch seconds of last client activity (for idle timeout).
pub(super) last_activity: AtomicU64,
/// Daemon start time (epoch seconds).
pub(super) start_time: u64,
/// Global stats collector.
pub(super) stats: StatsCollector,
/// Phase-level profiler for hot-path breakdown.
pub(super) profiler: PhaseProfiler,
/// On-disk artifact cache for hardlink optimization on cache hits.
pub(super) artifact_dir: NormalizedPath,
/// On-disk path for the persisted [`MetadataCache`] snapshot.
///
/// Written on flush (`Clear`) and shutdown (`Shutdown`); read at
/// daemon startup so warm-side daemons spawned after `soldr load`
/// start with their fast path already populated instead of an
/// empty `DashMap`. See `crate::fscache::persistence`.
pub(super) metadata_path: NormalizedPath,
/// Path used by [`CompilerHashCache`] for persistent (path, mtime, size,
/// hash) snapshots. Issue #517 — eliminates the ~50-60 ms cold-path
/// blake3 of the rustc binary on every first-after-restart compile.
/// Loaded by `Lifecycle::new`, written on shutdown alongside
/// `metadata.bin`.
pub(super) compiler_hash_cache_path: NormalizedPath,
/// Path used by [`SystemIncludeCache`] for persistent `(compiler_path,
/// mtime, size) -> include_paths` snapshots. Issue #541 — saves the
/// ~30-50 ms `<compiler> -v -E -x c++ NUL` spawn on every
/// first-after-restart C/C++ compile. Loaded by `Lifecycle::new`,
/// written on graceful shutdown alongside `metadata.bin`.
pub(super) system_includes_cache_path: NormalizedPath,
/// Temporary directory for injected depfiles.
pub(super) depfile_tmpdir: NormalizedPath,
/// Ultra-fast hit cache: context_key → (clock, artifact_key_hex, timestamp).
/// When the journal clock hasn't advanced since the last verified hit,
/// we skip all stat/hash/depgraph work and jump straight to artifact lookup.
pub(super) fast_hit_cache: DashMap<ContextKey, FastHitEntry>,
/// Whether the file watcher is active. Fast-hit cache is only used when
/// the watcher is running, since we rely on it for change detection.
pub(super) watcher_active: AtomicBool,
/// Response file expansion cache keyed by canonical root path.
/// Each entry carries the transitive response-file hashes required to
/// validate freshness before reusing the cached expansion.
pub(super) rsp_cache: DashMap<NormalizedPath, RspCacheEntry>,
/// Request-level fast path cache: hash(compiler, args, cwd) → pre-computed context.
/// When the same compile request is seen again and the fast-hit cache still
/// holds a valid entry, this allows skipping ALL heavy work: system include
/// discovery, watch_directories, response file expansion, arg parsing,
/// context building, and dep_graph registration.
pub(super) request_cache: DashMap<ContentHash, RequestCacheEntry>,
/// Session-level worktree-root cache resolved once at SessionStart.
pub(super) session_worktree_roots: DashMap<SessionId, SessionWorktreeRoot>,
/// Cross-root request-cache validation: (request fingerprint, root) -> last
/// verified artifact and journal clock. This lets repeated sibling hits
/// validate with journal checks instead of re-hashing every input.
pub(super) request_validation_cache: DashMap<RequestValidationKey, RequestValidationEntry>,
/// Compiler executable hash cache keyed by compiler path.
pub(super) compiler_hash_cache: CompilerHashCache,
/// Pre-filter for watch_directories: raw (non-canonicalized) paths we've
/// already processed. Avoids expensive canonicalize() syscalls (~1-5ms each
/// on Windows) for directories that are already being watched.
pub(super) watched_raw_dirs: DashMap<NormalizedPath, ()>,
/// PCH source registry: pch_output_path → source_header_path.
/// When a PCH generation succeeds, we record the mapping so that
/// consuming compilations can hash the source header instead of the
/// non-deterministic PCH binary.
pub(super) pch_source_map: DashMap<NormalizedPath, NormalizedPath>,
/// JSONL compile journal for build replay.
pub(super) journal: CompileJournal,
/// Bytes currently in spawn_blocking persistence tasks, invisible to eviction.
pub(super) in_flight_bytes: AtomicUsize,
/// Limits concurrent disk persistence tasks to prevent memory pileup
/// when disk I/O is slow and compilation requests are fast.
pub(super) persist_semaphore: Arc<tokio::sync::Semaphore>,
/// In-memory artifact index (bincode blob-backed) for fast startup and
/// persistence. Hot-path reads and writes go through `state.artifacts`;
/// this store holds the same data and snapshots it to disk periodically.
///
/// Arc-wrapped so the background index-writer task (see `index_writer_tx`)
/// can hold its own clone for batched `insert` calls without contending
/// with the request-handler path.
pub(super) artifact_store: Arc<ArtifactStore>,
/// Sender to the background index-writer task. Persist call-sites push
/// `(key_hex, ArtifactIndex)` pairs here and return immediately; the
/// writer task drains the channel and flushes to the on-disk blob in
/// batches.
///
/// Decouples the artifact-persist semaphore (which gates concurrent disk
/// writes) from the periodic index snapshot, so a slow flush no longer
/// holds a persist permit while other artifacts wait. See
/// `tests/persist_pool_bench.rs` for the data motivating this split.
pub(super) index_writer_tx: tokio::sync::mpsc::UnboundedSender<(String, ArtifactIndex)>,
/// Notify the index-writer to drain its WAL and exit on graceful shutdown.
/// Without this, the writer would only see the channel close after every
/// `Arc<SharedState>` ref (including those held by spawned persist tasks)
/// drops — which can race with runtime abort and lose unflushed entries.
pub(super) index_writer_shutdown: Arc<Notify>,
/// Whether the background artifact loading has completed.
pub(super) artifacts_loaded: AtomicBool,
/// Fingerprint manager: tracks per-watch dirty state for `zccache fp` commands.
pub(super) fingerprint: FingerprintManager,
/// Whether the in-memory dep graph is backed by a persisted snapshot.
///
/// Set to `true` when the graph is loaded from disk on startup (via
/// `set_dep_graph`) or when a periodic/shutdown save completes
/// successfully. Surfaced in `DaemonStatus.dep_graph_persisted` so the
/// CLI can distinguish "persisted graph" from "first-run, not yet flushed"
/// without inferring it from the on-disk file size.
pub(super) dep_graph_persisted: AtomicBool,
/// Optional load-time warning to mirror into every session log.
///
/// Populated by `set_depgraph_load_warning` when the daemon's startup load
/// of the persisted depgraph fell back to a cold session (version
/// mismatch, corrupt header, or unexpected I/O error). The string is
/// emitted once per session into the per-session log (`last-session.log`)
/// at `SessionStart` time so the cold fallback is never silent. Issue #320.
pub(super) depgraph_load_warning: Mutex<Option<String>>,
/// In-flight `Request::GenericToolExec` coalescing map (issue #272).
///
/// Concurrent callers with the same exec cache key share a `Notify` here:
/// the first caller spawns the tool and inserts; subsequent callers wait
/// on the same `Notify` and re-attempt the cache lookup once it fires,
/// guaranteeing the tool runs exactly once for the herd.
pub(super) in_flight_exec: DashMap<String, Arc<Notify>>,
/// Pending cache-write registry (issue #610, DD-025 condition 1).
///
/// Keyed by `artifact_key_hex` — every cold-miss path that defers its
/// `state.artifacts` insert into a `tokio::spawn` task **must** register
/// an entry here *before* spawning and notify+remove after the spawned
/// work completes. Concurrent lookups for the same key check the
/// registry first: they may wait briefly on the `Notify` (~5 ms ceiling
/// per condition 3's blast-radius bound) and then either re-attempt the
/// in-memory lookup or fall through to "miss → recompile". The failure
/// mode (DD-025 condition 2) is always a miss, never a wrong-hit — the
/// artifact's content identity stays bound by `blake3` (DD-005); only
/// the *publication* is deferred.
///
/// At rest the map is empty. Entries live ≤ 100 ms (30× p99 of
/// `depgraph_update_ns + persist_enqueue` from #605 iter T2). At most
/// `persist_semaphore.available_permits()` entries may exist
/// concurrently — the same semaphore that bounds existing C/C++ persist
/// spawns provides the backpressure.
///
/// On daemon restart the registry is empty: recovered state comes from
/// the WAL + on-disk artifacts (DD-008 / DD-017). Crash-mid-flight
/// safety is verified by the adversarial test
/// `crash_mid_flight_recovery_never_surfaces_wrong_content` in
/// `daemon/server/tests/deferred_cold_path.rs` (PR #618).
///
pub(super) pending_cache_writes: DashMap<String, Arc<Notify>>,
}