From 4a9210e1ec3ec2a8d2eab06c7d5e38b1a060a536 Mon Sep 17 00:00:00 2001 From: cinereal Date: Thu, 18 Jun 2026 16:38:49 +0200 Subject: [PATCH] nixos/tests: add nspawn-daemon-reexec-dbus repro Loops `systemctl daemon-reexec` inside a systemd-nspawn test container and asserts the in-container D-Bus stays usable afterwards. On affected systemd under nspawn, one of the re-execs wedges PID 1 (it never finishes re-initialising after the re-exec), and every later `systemctl` call hangs or returns 'Transport endpoint is not connected'. The identical loop on a QEMU node survives indefinitely, so the test is nspawn-specific. Assisted-by: Claude:claude-opus-4-8 --- nixos/tests/all-tests.nix | 1 + nixos/tests/nspawn-daemon-reexec-dbus.nix | 79 +++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 nixos/tests/nspawn-daemon-reexec-dbus.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5b17b838e343..650dd7205eec 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -152,6 +152,7 @@ in ssh-backdoor = runTestOn [ "x86_64-linux" ] ./nixos-test-driver/ssh-backdoor.nix; console-log = runTest ./nixos-test-driver/console-log.nix; containers = runTest ./nixos-test-driver/containers.nix; + nspawn-daemon-reexec-dbus = runTest ./nspawn-daemon-reexec-dbus.nix; skip-typecheck = runTest ./nixos-test-driver/skip-typecheck.nix; options-doc-regression = import ./nixos-test-driver/options-doc-regression.nix { inherit pkgs; }; driver-timeout = diff --git a/nixos/tests/nspawn-daemon-reexec-dbus.nix b/nixos/tests/nspawn-daemon-reexec-dbus.nix new file mode 100644 index 000000000000..115deb778bb5 --- /dev/null +++ b/nixos/tests/nspawn-daemon-reexec-dbus.nix @@ -0,0 +1,79 @@ +# Regression test for an nspawn-only systemd re-exec failure that broke D-Bus. +# +# Demonstrates `systemctl show` keeps working on `daemon-reexec`. +# +# Trigger: `systemctl daemon-reexec` issues a D-Bus `Manager.Reexecute`, like +# `switch-to-configuration-ng` on a systemd package change. +# +# Root cause: inside nspawn test containers PID 1 re-send `READY=1` on the +# `NOTIFY_SOCKET` on re-exec. The test driver stopped draining that socket +# after boot, so until drained its receive buffer filled and `systemctl` hung / +# errored `Failed to connect to bus: Transport endpoint is not connected`. +{ ... }: +{ + name = "nspawn-daemon-reexec-dbus"; + + # `containers.` => systemd-nspawn machine (vs `nodes.` => QEMU). + # An empty container boots full systemd + D-Bus, which is all we need. + containers.machine = { }; + + testScript = # python + '' + import re + + BUS_BROKEN = re.compile( + r"Transport endpoint is not connected|Failed to connect to bus" + ) + + # Without the fix the notify socket's receive buffer fills after 10 + # undrained `READY=1` resends, so PID 1 blocks then. + REEXECS = 10 + + + def bus_broken(): + """Whether the in-container D-Bus is unusable. A broken bus prints a + transport error or hangs until `timeout` kills it (status 124); both + count as broken.""" + status, out = machine.execute( + "timeout 10 systemctl show -p ActiveState --value " + "multi-user.target 2>&1", + check_return=False, + timeout=20, + ) + return status != 0 or bool(BUS_BROKEN.search(out)), status, out + + + machine.start() + machine.wait_for_unit("multi-user.target", timeout=120) + + # Pre-reexec sanity: the bus works and shows no break. + broken, status, out = bus_broken() + assert not broken, ( + f"bus already broken before any reexec: status={status} out={out!r}" + ) + machine.log(f"pre-reexec sanity OK: {out.strip()!r}") + + broke_at = None + for i in range(1, REEXECS + 1): + # The same D-Bus Manager.Reexecute that switch-to-configuration issues + # on a systemd change. + machine.execute( + "timeout 30 systemctl daemon-reexec", + check_return=False, + timeout=45, + ) + broken, status, out = bus_broken() + machine.log(f"[reexec {i}] status={status} out={out.strip()!r}") + if broken: + broke_at = i + break + + assert broke_at is None, ( + f"nspawn D-Bus broke after daemon-reexec #{broke_at} of {REEXECS} " + "(systemctl hung or returned a bus transport error). The re-exec'd " + "PID 1 never finished re-initialising -- the test driver stopped " + "draining the notify socket, so PID 1's READY=1 resend blocked. " + "Never observed on QEMU." + ) + ''; +}