Skip to main content

Module reload

Module reload 

Source
Expand description

Replacing a running configuration without restarting the process.

serve_on_with builds everything exactly once — profiles, deduplicated signer backends, filter chains, challenge registries, both routers — so until this module existed, every configuration change was a process restart. For the keys operators actually touch (a [filter] rule, an [ipam] token, a [notify] webhook, a renewed certificate) that dropped in-flight ACME orders and every live connection for a change that never needed a new socket.

A reload is a rebuild and swap, not a mutation. Everything is constructed and validated first; only once all of it succeeded is anything published. The publishing itself goes through tokio::sync::watch cells, and that choice is load-bearing rather than stylistic: watch::Sender::send_replace is synchronous, so a run of sends with no .await between them cannot be observed half-applied — no other task can run in the middle of it.

What a swap cannot honour is refused by name, and the whole reload is refused with it. Exactly one key is left in that category — database.url, the pool being open and the accounts and orders issued against it not following a URL elsewhere. Refusing by name is the posture startup already takes when it rejects an unknown logging.target rather than falling back.

Everything else that used to be on that list came off the same way — the thing said to be unmovable was made movable rather than argued with:

  • [logging], the tracing subscriber being installed once per process. server::logging now installs the whole stack behind a tracing_subscriber::reload::Layer, so all six keys swap with everything else. The publishing run does it first, since an operator who raised the level did it to see what happens next — starting with the reload’s own line.
  • The sockets. acme_proxy_net::listener owns the accept loop, so a role’s TcpListener is replaceable and its TLS mode is read per connection: all five of server.bind_address, admin.enabled, admin.bind_address and both tls.enabled flips reload, as do the two [metrics] keys. Binding happens while a failure can still refuse the reload, so a bad address is answered by a socket that never moved.
  • [jobs], the runner having snapshotted its pacing at spawn. It now re-derives that pacing from a watch cell on every pass of its loop and resizes its own concurrency pool, and the queue reads max_attempts from a shared atomic — so all seven keys reload, and none of them was ever physically frozen the way the pool is. These are the knobs an operator reaches for mid-incident (slow a retry storm, widen a lease, raise concurrency), which made them the worst possible thing to charge a restart for. See acme_proxy_jobs::jobs::runner.
  • The profile set, each profile’s [signer], and [dns]/[proxy] — the last four and the hardest, because a signer backend used to own state with no durable home: a LocalCa’s revocation ledger and a relay’s http-01 token store. Both now live in the database, which the outgoing and the incoming backend share, so a backend whose configuration moved is simply rebuilt — a revocation landing mid-reload is in the table the new instance reads, and a challenge fetch in flight is answered from the same rows. A backend whose configuration did not move is reused verbatim rather than rebuilt. Mounting and unmounting an endpoint fell out of it for free, that having been the whole of what made the profile set unmovable, and [dns]/[proxy] fell out too: they were frozen only because the signers cached them at construction, which is now a reason to rebuild a signer (they are part of its identity key) rather than to refuse the edit.

Structs§

Applied
One resolved configuration, as the frozen-key check sees it.
ReloadHandle
The handle a signal handler (or a future admin route) triggers reloads with.
ReloadReport
What a completed reload did.
ReloadRequest
One request to reload, and where to send the answer.
Reloads
The receiving half, held by the serving path.

Enums§

ReloadError
Why a reload did not happen.

Functions§

channel
Opens a reload channel.
check_frozen
Refuses proposed by name if it changes anything [FROZEN] covers.
router_channel
Opens a router cell, with initial as its first generation.
swappable
Wraps a router cell as one servable Router.