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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! `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,
/// running-process BackendHandle identity served on the same direct
/// daemon endpoint for the minimal broker-adoption path. Slice 24
/// of zccache#782: migrated to the `protocol_v2::backend_handle`
/// namespace (upstream re-export of the cross-version-stable type).
pub(super) backend_identity:
running_process::broker::protocol_v2::backend_handle::DaemonProcess,
/// 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>,
/// Issue #813 / #816 — global compile-concurrency cap.
/// `Some(sem)` enforces an upper bound on the number of compiler
/// child processes the daemon will spawn at once across ALL
/// clients; cap is `max(1, num_cpus - 1)` on interactive hosts,
/// `num_cpus` on CI, overridable via `ZCCACHE_MAX_PARALLEL_COMPILES`.
/// `None` when the override is `0` (or `unlimited`) — preserves the
/// historical uncapped behavior for users who want it.
pub(super) compile_concurrency: Option<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,
/// Whether the background compiler-hash-cache load has completed.
///
/// Issue #784: the on-disk snapshot is loaded post-lockfile from a
/// `spawn_blocking` task. The shutdown save path checks this before
/// calling `save_to_disk` — saving while the load is still pending
/// could write a partial snapshot over the persisted file. False
/// until the loader's `install()` runs; once true the in-memory
/// DashMap is considered canonical for save.
pub(super) compiler_hash_cache_loaded: AtomicBool,
/// Whether the background `metadata.bin` load has completed.
///
/// Issue #784 phase 2b: the on-disk metadata cache is loaded
/// post-lockfile from a `spawn_blocking` task. The shutdown save
/// path in `run.rs` checks this before persisting — saving while
/// the load is still pending could write a partial snapshot over
/// the on-disk file. Mirrors `compiler_hash_cache_loaded` above.
pub(super) metadata_cache_loaded: AtomicBool,
/// Whether the background system-include-cache load has completed.
///
/// Issue #784 phase 2c: the on-disk snapshot is loaded post-lockfile
/// from a `spawn_blocking` task. The shutdown save path in `run.rs`
/// checks this before persisting — saving while the load is still
/// pending could write a partial snapshot over the on-disk file.
/// Mirrors `compiler_hash_cache_loaded` above.
pub(super) system_includes_loaded: AtomicBool,
/// Whether the on-disk artifact-index blob has been merged into
/// the live `artifact_store`.
///
/// Issue #784 phase 2d: `bind_with_cache_dir` constructs the store
/// empty; a background `spawn_blocking` calls `load_from_disk`
/// after the readiness lockfile. `lookup_artifact_with_disk_fallback`
/// (in `util.rs`) also triggers a synchronous `load_from_disk` on
/// the first cache-miss in the load window so the existing
/// disk-fallback contract holds. Either call site flips this to
/// `true` on completion; subsequent misses short-circuit instead
/// of re-reading the blob.
pub(super) artifact_store_loaded: AtomicBool,
/// Whether the `died-shutdown` lifecycle event has been written for this
/// daemon. Under burst load (issue #726), many wedge-detecting clients
/// race to send `Request::Shutdown` within a few milliseconds and each
/// connection handler would otherwise write the same event — 25+ duplicate
/// rows for a single death observed. Guard the write with a compare-and-swap
/// so only the first Shutdown handler logs.
pub(super) shutdown_event_logged: AtomicBool,
/// Latched once any shutdown source fires. Background tasks use this
/// instead of consuming the public shutdown Notify, so legacy
/// `shutdown_handle().notify_one()` callers still wake the accept loop.
pub(super) shutdown_requested: 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,
/// Whether startup has finished classifying the persisted depgraph.
///
/// `zccache-daemon` marks this false before it moves the disk load onto a
/// background thread. Compile requests must not register/check contexts
/// against the empty default graph while this is false, otherwise the
/// first warm lookup races into the `cold_skip` path even though a valid
/// persisted graph is about to be installed. Issue #798.
pub(super) dep_graph_load_complete: AtomicBool,
/// Wakes compile requests waiting for startup depgraph classification.
pub(super) dep_graph_load_notify: Arc<Notify>,
/// 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>>,
/// In-memory exec-probe/store cache for caller-owned tool caching
/// (issue #838 slice 1). Keyed by hex-encoded blake3 cache key,
/// values are the opaque result bytes the caller supplied via
/// `Request::ExecStore`. Slice 1 keeps this in-memory only; a
/// follow-up slice swaps to `KvStore` for persistence.
pub(super) exec_cache: DashMap<String, Arc<Vec<u8>>>,
}