Skip to content

Zero-downtime binary upgrade

Dwara supports swapping the gateway binary under load with zero failed requests and zero reset connections. The mechanism is a SO_REUSEPORT (a socket option that lets two processes bind the same port simultaneously) hand-off triggered by SIGUSR2 (a Unix signal used here to trigger the upgrade): the old process spawns a new copy of itself, both bind the same ports, the new process starts accepting, and the old process drains and exits.

When to use this

Use this when you need to swap the gateway binary under live traffic without a single failed request — for example during security patching or version upgrades on a production gateway where even a brief blip is unacceptable. The alternative is a normal SIGTERM restart with a load balancer in front doing health-check-based drain: the balancer stops sending traffic to the old instance once /readyz goes 503, then you restart. That is simpler and good enough when a few seconds of downtime-per-instance is tolerable; the zero-downtime upgrade is for single-instance deployments or cases where no fronting balancer can absorb the hand-off.

How it works

The upgrade is a hand-off between two processes sharing the same listening ports; the old process only exits after the new one is already serving:

  1. Every listening socket is bound with SO_REUSEPORT (in addition to SO_REUSEADDR (allows rebinding a port in TIME_WAIT)). This allows a second process to bind the same port while the first is still listening. On Linux the kernel load-balances accepts across both sockets; on macOS both sockets may accept (the hand-off still works).
  2. On SIGUSR2, the old process spawns a new copy of the binary (the same path by default, or DWARA_UPGRADE_BINARY). The child inherits the environment (DWARA_CONFIG, DWARA_BIND, ...), so it serves the same listeners.
  3. The new process binds its listeners (SO_REUSEPORT lets it bind alongside the old), spawns its accept tasks, then signals READY to the old process over a Unix domain socket.
  4. The old process receives READY and runs the same drain sequence as SIGTERM: stop accepting, flush kernel backlogs, drain HTTP connections within the shutdown budget, exit 0. Because the new process is already accepting, no connection is refused and no in-flight connection is reset.
  5. If the new process fails to start or does not signal READY within the timeout, the old process logs the error and keeps running — a failed upgrade never takes the gateway down.

Triggering an upgrade

With the CLI

Start the gateway with DWARA_PID_FILE set so the CLI can find it:

sh
DWARA_PID_FILE=/run/dwara.pid dwara --config /etc/dwara/dwara.yaml

Install the new binary (replace the file on disk), then:

sh
dwara-cli upgrade

The CLI reads the PID from the PID file and sends SIGUSR2. You can also pass the PID explicitly:

sh
dwara-cli upgrade --pid 12345
dwara-cli upgrade --pid-file /run/dwara.pid

The command only delivers the signal — the hand-off is asynchronous. Watch the gateway logs to confirm:

upgrade_initiated  SIGUSR2 received: spawning new binary ...
upgrade_child_spawned  upgrade child spawned; waiting for READY signal
upgrade_ready  new process signaled READY; the old process will drain and exit
drained, exiting

With kill directly

sh
kill -USR2 $(cat /run/dwara.pid)

Environment variables

VariableDefaultPurpose
DWARA_PID_FILEunsetWrite the process PID here on startup. The dwara upgrade CLI reads it to find the process to signal. The new process overwrites it after signaling READY.
DWARA_UPGRADE_BINARYcurrent executablePath to the new binary for an upgrade. Override to swap in a different binary.
DWARA_UPGRADE_READY_TIMEOUT_SECS30How long the old process waits for the new process to signal READY before giving up (and keeping the old process running).

systemd

For a systemd-managed gateway, the upgrade is operator-driven (not auto-restarted by systemd). Set DWARA_PID_FILE in the unit and run dwara-cli upgrade (or kill -USR2) after replacing the binary. The old process exits 0; systemd may restart it depending on Restart= — to avoid a double-start, set Restart=on-failure (exit 0 does not trigger a restart) or use Type=exec with ExecReload wired to the upgrade signal.

Limitations

  • Passthrough and L4 splices drain over the shutdown budget (the same as SIGTERM): the old process waits for in-flight byte relays to complete up to DWARA_SHUTDOWN_TIMEOUT_SECS. The gateway does not terminate TLS in passthrough mode, so it cannot emit a TLS close_notify alert; whatever remains at the deadline is force-closed.
  • The listener bind set is fixed at startup (address/port). An upgrade inherits the same listeners; changing the bind set still requires a full restart.
  • SO_REUSEPORT is available on Linux and macOS. The hand-off is portable across both; Linux's load-balancing is more even.

Runnable demo

Run this feature against a live gateway: demos/09-operations/ (test script: test-09-zero-downtime-upgrade.sh) in the repository. The category README covers prerequisites and teardown.