Expand description
Hot-reload of mutable configuration fields on SIGHUP.
Reloadable fields (applied in place; no restart required):
observability.log_levelcache.pinned_hashes- all of
security.*— the liveConnectionLimiterswaps itsArc<Semaphore>wholesale on reload (already-held permits drain into the previous semaphore on drop; new acquires hit the new one) and rebuilds its keyedgovernorrate limiter from the new quota, swapping it in under anRwLock. Token-bucket state is not preserved across the rebuild.0in anysecurity.*field disables that layer. - the
[content]denylist (content.denied_hashes,content.denied_origins) — hot reload here is load-bearing, not a convenience: ADR 011 sizes this mechanism to the one-hour removal clock, so a takedown must never wait on a daemon restart. - all of
[load_shed]— the liveLoadShedControllerswaps its policy in place.
This list is the authority; NodeCommand::Reload’s help text in
decdn-common repeats it for operators, so extend both together.
Any non-reloadable field the file carries gets a “requires restart” message (logged on presence, not on change) — the runtime would otherwise need to tear down the iroh endpoint, the metrics listener, etc., which is far beyond the scope of a quick reload.
The reload entry point (RuntimeReloadState::reload) is also
called directly by tests, so its only side effects are mutating
values inside RuntimeReloadState and emitting tracing events.
It does not touch any global state.
On parse error, previous values are retained: the reload is best-effort and a malformed file must never crash a running node.
§Section trait
Each reloadable knob is a ReloadableSection impl. The trait
drives a fixed three-phase iteration in RuntimeReloadState::reload:
- Resolve every section. A single
ConfigDiagnosticsis threaded through every section’sresolve, so an operator who broke multiple fields sees them all in one error rather than fixing them one SIGHUP at a time, and collapsed once at the end; any problem → return early with previous values retained for every section (the all-or-nothing contract). Resolved values are stashed in a per-section buffer cell (Mutex<Option<Self::Resolved>>) so the trait staysdyn-safe despite each section having its ownResolvedtype; every section populates its buffer on every resolve (a sentinel placeholder if it pushed to the bag). The early return skips the swap phase, and the next reload’sclear_bufferevicts any leftover sentinel. - Run every
fallible_commit. The log-level filter swap is the only currently-fallible commit. This phase is the rollback boundary: commits already applied stay applied; a later failure aborts the rest. The trait makes this boundary explicit. - Run every
infallible_swap. Atomic stores,ArcSwapswaps,ConnectionLimiter::reload, and the per-section “applied” tracing event live here. None can fail.
The buffer cell is emptied at the start of every reload(), filled
by resolve, read by fallible_commit, and drained by
infallible_swap. A panic between resolve and infallible_swap
would leave a buffer populated; the next reload()’s clear step
evicts it before anything else runs.
Structs§
- Reload
Snapshot - Read-only snapshot of the reloadable fields, returned by
RuntimeReloadState::current. Used byadmin_v1_reloadto report what’s running after a successful reload — operators get back the values their RPC just applied without having to scrape logs. - Runtime
Reload State - Shared, mutable handles for the fields the runtime can hot-reload.
Enums§
- LogLevel
Apply - What a
LogLevelSetterdid with a requested level.
Type Aliases§
- LogLevel
Setter - Closure that swaps the live
EnvFilterto one matchinglevel.