Skip to main content

Module reload

Module reload 

Source
Expand description

Hot-reload of mutable configuration fields on SIGHUP.

Reloadable fields (applied in place; no restart required):

  • observability.log_level
  • cache.pinned_hashes
  • all of security.* — the live ConnectionLimiter swaps its Arc<Semaphore> wholesale on reload (already-held permits drain into the previous semaphore on drop; new acquires hit the new one) and rebuilds its keyed governor rate limiter from the new quota, swapping it in under an RwLock. Token-bucket state is not preserved across the rebuild. 0 in any security.* 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 live LoadShedController swaps 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:

  1. Resolve every section. A single ConfigDiagnostics is threaded through every section’s resolve, 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 stays dyn-safe despite each section having its own Resolved type; 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’s clear_buffer evicts any leftover sentinel.
  2. 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.
  3. Run every infallible_swap. Atomic stores, ArcSwap swaps, 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§

ReloadSnapshot
Read-only snapshot of the reloadable fields, returned by RuntimeReloadState::current. Used by admin_v1_reload to report what’s running after a successful reload — operators get back the values their RPC just applied without having to scrape logs.
RuntimeReloadState
Shared, mutable handles for the fields the runtime can hot-reload.

Enums§

LogLevelApply
What a LogLevelSetter did with a requested level.

Type Aliases§

LogLevelSetter
Closure that swaps the live EnvFilter to one matching level.