diff --git a/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf b/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf
new file mode 100644
index 0000000000..ab3687743c
--- /dev/null
+++ b/mkosi/mkosi.extra/usr/share/dbus-1/system.d/systemd.test.DBusReconnect.conf
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/meson.build b/src/test/meson.build
index da3148d955..d0d4f25f17 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -315,6 +315,10 @@ executables += [
'dependencies' : libopenssl_cflags,
'conditions' : ['HAVE_OPENSSL'],
},
+ test_template + {
+ 'sources' : files('test-dbus-name-holder.c'),
+ 'type' : 'manual',
+ },
test_template + {
'sources' : files('test-display-quota.c'),
'type' : 'manual',
diff --git a/src/test/test-dbus-name-holder.c b/src/test/test-dbus-name-holder.c
new file mode 100644
index 0000000000..011ce782ea
--- /dev/null
+++ b/src/test/test-dbus-name-holder.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-bus.h"
+#include "sd-daemon.h"
+
+#include "log.h"
+#include "main-func.h"
+#include "time-util.h"
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ if (argc < 3 || argc > 5)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Usage: %s BUSNAME READY_MARKER [RELEASE_MARKER] [NOTIFY_MARKER]",
+ program_invocation_short_name);
+
+ const char
+ *name = argv[1],
+ *marker = argv[2],
+ *release_marker = argc > 3 ? argv[3] : NULL,
+ *notify_marker = argc > 4 ? argv[4] : NULL;
+
+ for (;;) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open system bus: %m");
+
+ r = sd_bus_request_name(bus, name, /* flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire dbus name '%s': %m", name);
+
+ log_info("Acquired dbus name '%s'.", name);
+
+ for (;;) {
+ r = sd_bus_process(bus, /* ret= */ NULL);
+ if (r < 0)
+ break;
+ if (r > 0)
+ continue;
+
+ if (release_marker && access(release_marker, F_OK) >= 0) {
+ r = sd_bus_release_name(bus, name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to release dbus name '%s': %m", name);
+
+ log_info("Released dbus name '%s'.", name);
+ release_marker = NULL; /* go back to blocking until we're killed */
+ }
+
+ r = sd_bus_wait(bus, release_marker ? 100 * USEC_PER_MSEC : USEC_INFINITY);
+ if (r < 0)
+ break;
+ }
+
+ log_info("Disconnected from bus, reconnecting...");
+
+ while (access(marker, F_OK) < 0) {
+ if (notify_marker && access(notify_marker, F_OK) >= 0) {
+ r = sd_notify(/* unset_environment= */ false, "RELOADING=1\nREADY=1");
+ if (r < 0)
+ return log_error_errno(r, "Failed to send reload notification: %m");
+
+ log_info("Sent reload notification.");
+ notify_marker = NULL;
+ }
+
+ usleep_safe(50 * USEC_PER_MSEC);
+ }
+ }
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh b/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh
new file mode 100755
index 0000000000..9a64131cd5
--- /dev/null
+++ b/test/units/TEST-23-UNIT-FILE.dbus-reconnect.sh
@@ -0,0 +1,161 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
+holder=/usr/lib/systemd/tests/unit-tests/manual/test-dbus-name-holder
+name=systemd.test.DBusReconnect
+marker=/run/dbus-reconnect-go
+release=/run/dbus-reconnect-release
+notify=/run/dbus-reconnect-notify
+reloaded=/run/dbus-reconnect-reloaded
+
+at_exit() {
+ rm -f "$marker" "$release" "$notify" "$reloaded"
+ systemctl stop \
+ dbus-reconnect-recover.service \
+ dbus-reconnect-stop.service \
+ dbus-reconnect-exit.service \
+ dbus-reconnect-drop.service \
+ dbus-reconnect-notify.service \
+ dbus-reconnect-notify-target.service \
+ dbus-reconnect-reexec.service 2>/dev/null || :
+}
+
+trap at_exit EXIT
+
+# Poll until property $2 of unit $1 reaches value $3
+poll() {
+ local unit="${1:?}" prop="${2:?}" want="${3:?}" got i
+ for ((i = 0; i < 150; i++)); do
+ got="$(systemctl show -p "$prop" --value "$unit")"
+ if [[ "$got" == "$want" ]]; then
+ return 0
+ fi
+ sleep 0.2
+ done
+ echo "poll: timed out waiting for $unit $prop=$want (last: $got)" >&2
+ return 1
+}
+
+reset_failed() {
+ local unit="${1:?}"
+
+ systemctl reset-failed "$unit" 2>/dev/null || :
+}
+
+restart_broker() {
+ systemctl kill --signal=KILL dbus.service
+ timeout 90 bash -c '
+ until busctl --system status >/dev/null 2>&1; do
+ systemctl reset-failed dbus.service dbus.socket 2>/dev/null
+ systemctl start dbus.socket 2>/dev/null
+ sleep 0.2
+ done'
+}
+
+enter_revalidation() {
+ local unit="${1:?}"
+
+ shift
+ reset_failed "$unit"
+ rm -f "$marker"
+ systemd-run \
+ --unit="$unit" \
+ -p Type=dbus \
+ -p BusName="$name" \
+ -p Restart=no \
+ "$@" \
+ "$holder" "$name" "$marker"
+ restart_broker
+ poll "$unit" SubState running-revalidating
+}
+
+# the name comes back within the grace window
+unit=dbus-reconnect-recover.service
+enter_revalidation "$unit"
+main_pid="$(systemctl show -p MainPID --value "$unit")"
+assert_neq "$main_pid" "0"
+touch "$marker"
+poll "$unit" SubState running
+assert_eq "$(systemctl show -p MainPID --value "$unit")" "$main_pid"
+systemctl stop "$unit"
+
+# the name never comes back
+unit=dbus-reconnect-stop.service
+enter_revalidation "$unit"
+poll "$unit" ActiveState inactive
+
+# ditto with RemainAfterExit=yes
+unit=dbus-reconnect-exit.service
+enter_revalidation "$unit" -p RemainAfterExit=yes
+poll "$unit" SubState exited
+systemctl stop "$unit"
+
+# the name is dropped on a live connection and we action it at once
+unit=dbus-reconnect-drop.service
+reset_failed "$unit"
+rm -f "$release"
+systemd-run \
+ --unit="$unit" \
+ -p Type=dbus \
+ -p BusName="$name" \
+ -p Restart=no \
+ "$holder" "$name" "$marker" "$release"
+poll "$unit" SubState running
+touch "$release"
+for ((i = 0; i < 150; i++)); do
+ sub_state="$(systemctl show -p SubState --value "$unit")"
+ if [[ "$sub_state" == running-revalidating ]]; then
+ echo "live drop entered the revalidation window" >&2
+ exit 1
+ fi
+
+ active_state="$(systemctl show -p ActiveState --value "$unit")"
+ if [[ "$active_state" == inactive ]]; then
+ break
+ fi
+
+ sleep 0.2
+done
+assert_eq "$(systemctl show -p ActiveState --value "$unit")" inactive
+
+# a reload notification is propagated even while the name is absent (revalidating)
+target=dbus-reconnect-notify-target.service
+reset_failed "$target"
+systemd-run \
+ --unit="$target" \
+ -p Type=oneshot \
+ -p RemainAfterExit=yes \
+ -p "ExecReload=/usr/bin/touch $reloaded" \
+ /usr/bin/true
+
+unit=dbus-reconnect-notify.service
+reset_failed "$unit"
+rm -f "$marker" "$release" "$notify" "$reloaded"
+systemd-run \
+ --unit="$unit" \
+ -p Type=dbus \
+ -p BusName="$name" \
+ -p Restart=no \
+ -p NotifyAccess=main \
+ -p PropagatesReloadTo="$target" \
+ "$holder" "$name" "$marker" "$release" "$notify"
+restart_broker
+poll "$unit" SubState running-revalidating
+touch "$notify"
+timeout 30 bash -c "until [[ -e '$reloaded' ]]; do sleep 0.2; done"
+touch "$marker"
+poll "$unit" SubState running
+systemctl stop "$unit" "$target"
+
+# the revalidation state survives a daemon-reexec and still recovers afterwards
+unit=dbus-reconnect-reexec.service
+enter_revalidation "$unit"
+systemctl daemon-reexec
+touch "$marker"
+poll "$unit" SubState running
+systemctl stop "$unit"