rninja-daemon Architecture¶
rninja-daemon is the long-running process that holds parsed manifests,
dependency graphs, and build logs in memory between invocations of the CLI.
That is what gives rninja its sub-millisecond no-op build time: on a warm
daemon, the CLI just asks "has anything changed?" and the daemon already
knows.
The binary is defined in
src/bin/rninja-daemon.rs
and the implementation lives under
src/daemon/.
Module layout¶
From src/daemon/mod.rs:
| Module | Purpose |
|---|---|
daemon/server.rs |
NNG-based IPC server and main loop |
daemon/protocol.rs |
Request/response types, version constant |
daemon/session.rs |
Per-build session lifecycle (SessionManager) |
daemon/state.rs |
Cached manifests, graphs, build logs (DaemonState) |
daemon/watcher.rs |
notify-based file watcher for invalidation |
Transport¶
The daemon listens on an NNG Rep0 socket bound to a Unix domain socket
(or named pipe on Windows). The URL form is
ipc:///path/to/socket. The default path is derived from the user identity
and a stable hash, and can be overridden with
--daemon-socket on the CLI or RNINJA_DAEMON_SOCKET in the environment.
Wire format is MessagePack with a 4-byte big-endian length prefix. See Reference > IPC Protocol for the request/response schema.
Cached state¶
DaemonState (in daemon/state.rs) maintains one CachedManifest per
build directory:
struct CachedManifest {
manifest: Arc<Manifest>, // parsed build.ninja
graph: Arc<Graph>, // dependency DAG
build_log: Arc<RwLock<BuildLog>>, // hashes of last-built outputs
fingerprint: u64, // hash of build.ninja
last_validated: Instant,
included_files: Vec<PathBuf>, // for the file watcher
}
Defaults from DaemonConfig:
max_cached_manifests: 100manifest_ttl_secs: 300 (revalidation interval)max_concurrent_builds: 4
Lifecycle¶
stateDiagram-v2
[*] --> Starting
Starting --> Listening: bind socket
Listening --> Building: BuildRequest
Building --> Listening: build complete
Listening --> ManifestRefresh: TTL expired / watcher event
ManifestRefresh --> Listening
Listening --> ShuttingDown: shutdown / SIGTERM
ShuttingDown --> [*]
Concurrency¶
- One NNG socket, one accept loop.
SessionManagercaps concurrent builds atmax_concurrent_builds.- Each session gets a UUID, isolated state, and independent failure handling.
- The file watcher runs on its own thread and posts events into the daemon.
Auto-spawn¶
The CLI spawns the daemon on demand when it cannot connect to an existing
socket. This means users never have to run rninja-daemon manually -
the daemon's existence is an implementation detail of the CLI's
performance, not a deployment step.
--no-daemon (or short-lived environments like CI) bypasses this and
runs the executor in-process.
Multi-repo support¶
A single daemon can serve any number of build directories. Each repository
gets its own CachedManifest entry keyed on the absolute path. The file
watcher tracks every included file across all cached manifests and
invalidates entries on change.
See Daemon > Multi-Repository Support for operational notes.
Failure modes¶
- Stale socket file: removed on startup before binding (
server.rs). - NNG bind failure: logged and the process exits non-zero.
- Watcher exhaustion: falls back to TTL-based revalidation.
- Auth requirement misconfigured: daemon exits with an explicit error rather than silently allowing anonymous access.
Related¶
- Daemon > Overview - user-facing operational guide.
- Daemon > Auto-Spawn - how the CLI starts the daemon.
- Reference > IPC Protocol - wire format.