core: Don't racily stop Type=dbus services when the bus broker restarts (#42455)

This commit is contained in:
Yu Watanabe
2026-09-01 09:24:48 +09:00
committed by GitHub
11 changed files with 381 additions and 25 deletions
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!--
SPDX-License-Identifier: LGPL-2.1-or-later
-->
<busconfig>
<policy user="root">
<allow own="systemd.test.DBusReconnect"/>
</policy>
</busconfig>
+1
View File
@@ -233,6 +233,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_START] = "start",
[SERVICE_START_POST] = "start-post",
[SERVICE_RUNNING] = "running",
[SERVICE_RUNNING_REVALIDATING] = "running-revalidating",
[SERVICE_EXITED] = "exited",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_REFRESH_CREDENTIALS] = "refresh-credentials",
+1
View File
@@ -132,6 +132,7 @@ typedef enum ServiceState {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RUNNING_REVALIDATING, /* Temporarily waiting for the D-Bus name to reappear */
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_REFRESH_CREDENTIALS, /* ditto, but for credentials */
+1
View File
@@ -2048,6 +2048,7 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
return false;
if (!IN_SET(deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state,
SERVICE_RUNNING,
SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
+114 -21
View File
@@ -70,6 +70,8 @@
#define SERVICE_FD_STORE_POPULATED(s) (!!(s)->fd_store)
#define SERVICE_BUS_NAME_GRACE_USEC (2 * USEC_PER_SEC)
static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_DEAD] = UNIT_INACTIVE,
[SERVICE_CONDITION] = UNIT_ACTIVATING,
@@ -77,6 +79,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_START] = UNIT_ACTIVATING,
[SERVICE_START_POST] = UNIT_ACTIVATING,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_RUNNING_REVALIDATING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
@@ -111,6 +114,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_START] = UNIT_ACTIVE,
[SERVICE_START_POST] = UNIT_ACTIVE,
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_RUNNING_REVALIDATING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
@@ -140,6 +144,7 @@ static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t
static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata);
static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
static int service_dispatch_bus_name_grace(sd_event_source *source, usec_t usec, void *userdata);
static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
@@ -153,7 +158,7 @@ static void service_set_state(Service *s, ServiceState state);
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
@@ -175,7 +180,7 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
static bool SERVICE_STATE_WITH_WATCHDOG(ServiceState state) {
return IN_SET(state,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING);
@@ -213,6 +218,7 @@ static void service_init(Unit *u) {
s->oom_policy = _OOM_POLICY_INVALID;
s->reload_begin_usec = USEC_INFINITY;
s->revalidate_runtime_begin_usec = USEC_INFINITY;
s->reload_signal = SIGHUP;
s->fd_store_preserve_mode = EXEC_PRESERVE_RESTART;
@@ -618,6 +624,7 @@ static void service_done(Unit *u) {
service_stop_watchdog(s);
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
s->bus_name_grace_event_source = sd_event_source_disable_unref(s->bus_name_grace_event_source);
s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source);
s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot);
@@ -1668,6 +1675,9 @@ static void service_set_state(Service *s, ServiceState state) {
SERVICE_CLEANING))
s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source);
if (state != SERVICE_RUNNING_REVALIDATING)
s->bus_name_grace_event_source = sd_event_source_disable_unref(s->bus_name_grace_event_source);
if (!SERVICE_STATE_WITH_MAIN_PROCESS(state)) {
service_unwatch_main_pid(s);
s->main_command = NULL;
@@ -1781,6 +1791,14 @@ static int service_coldplug(Unit *u) {
if (r < 0)
return r;
if (s->deserialized_state == SERVICE_RUNNING_REVALIDATING) {
r = unit_arm_timer(UNIT(s), &s->bus_name_grace_event_source, /* relative= */ true,
SERVICE_BUS_NAME_GRACE_USEC,
service_dispatch_bus_name_grace);
if (r < 0)
return r;
}
if (pidref_is_set(&s->main_pid) &&
pidref_is_unwaited(&s->main_pid) > 0 &&
SERVICE_STATE_WITH_MAIN_PROCESS(s->deserialized_state)) {
@@ -2739,14 +2757,11 @@ static void service_enter_stop(Service *s, ServiceResult f) {
service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
}
static bool service_good(Service *s) {
static bool service_good_except_bus_name(Service *s) {
int main_pid_ok;
assert(s);
if (s->type == SERVICE_DBUS && !s->bus_name_good)
return false;
main_pid_ok = main_pid_good(s);
if (main_pid_ok > 0) /* It's alive */
return true;
@@ -2760,6 +2775,26 @@ static bool service_good(Service *s) {
return cgroup_good(s) != 0;
}
static bool service_good(Service *s) {
assert(s);
if (s->type == SERVICE_DBUS && !s->bus_name_good)
return false;
return service_good_except_bus_name(s);
}
static void service_enter_exited_or_stop(Service *s) {
assert(s);
s->revalidate_runtime_begin_usec = USEC_INFINITY;
if (s->remain_after_exit)
service_set_state(s, SERVICE_EXITED);
else
service_enter_stop(s, SERVICE_SUCCESS);
}
static void service_enter_running(Service *s, ServiceResult f) {
int r;
@@ -2782,7 +2817,17 @@ static void service_enter_running(Service *s, ServiceResult f) {
else {
service_set_state(s, SERVICE_RUNNING);
r = service_arm_timer(s, /* relative= */ false, service_running_timeout(s));
/* We're resuming. Recompute the budget from the current config (which was measured
* from when we entered revalidation). */
usec_t remaining = USEC_INFINITY;
if (s->revalidate_runtime_begin_usec != USEC_INFINITY)
remaining = usec_sub_unsigned(service_running_timeout(s), s->revalidate_runtime_begin_usec);
s->revalidate_runtime_begin_usec = USEC_INFINITY;
if (remaining != USEC_INFINITY)
r = service_arm_timer(s, /* relative= */ true, remaining);
else
r = service_arm_timer(s, /* relative= */ false, service_running_timeout(s));
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
service_enter_running(s, SERVICE_FAILURE_RESOURCES);
@@ -2790,10 +2835,33 @@ static void service_enter_running(Service *s, ServiceResult f) {
}
}
} else if (s->remain_after_exit)
service_set_state(s, SERVICE_EXITED);
else
service_enter_stop(s, SERVICE_SUCCESS);
} else if (s->type == SERVICE_DBUS && !s->bus_name_good && service_good_except_bus_name(s)) {
/* If we're already revalidating, just keep waiting on the timer we armed on the way in, so a
* flapping broker that reconnects repeatedly can't keep pushing the deadline out. */
if (s->state == SERVICE_RUNNING_REVALIDATING)
return;
log_unit_warning(UNIT(s),
"D-Bus name %s vanished (possibly from an unsupported broker restart), giving it %s to return.",
s->bus_name,
FORMAT_TIMESPAN(SERVICE_BUS_NAME_GRACE_USEC, USEC_PER_SEC));
r = unit_arm_timer(UNIT(s), &s->bus_name_grace_event_source, /* relative= */ true,
SERVICE_BUS_NAME_GRACE_USEC, service_dispatch_bus_name_grace);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to arm D-Bus name grace timer: %m");
service_enter_stop(s, SERVICE_FAILURE_RESOURCES);
return;
}
if (s->state == SERVICE_RUNNING)
s->revalidate_runtime_begin_usec = now(CLOCK_MONOTONIC);
service_set_state(s, SERVICE_RUNNING_REVALIDATING);
} else
service_enter_exited_or_stop(s);
}
static void service_enter_start_post(Service *s) {
@@ -3559,6 +3627,7 @@ static int service_start(Unit *u) {
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
s->reload_begin_usec = USEC_INFINITY;
s->revalidate_runtime_begin_usec = USEC_INFINITY;
s->status_text = mfree(s->status_text);
s->status_errno = 0;
@@ -3677,6 +3746,7 @@ static int service_stop(Unit *u) {
return 0;
case SERVICE_RUNNING:
case SERVICE_RUNNING_REVALIDATING:
case SERVICE_EXITED:
service_enter_stop(s, SERVICE_SUCCESS);
return 1;
@@ -3695,7 +3765,7 @@ static int service_stop(Unit *u) {
static int service_reload(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u));
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING, SERVICE_EXITED));
s->reload_result = SERVICE_SUCCESS;
s->refreshed_mask = 0;
@@ -3923,6 +3993,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
(void) serialize_usec(f, "watchdog-override-usec", s->watchdog_override_usec);
(void) serialize_usec(f, "reload-begin-usec", s->reload_begin_usec);
(void) serialize_usec(f, "revalidate-runtime-begin-usec", s->revalidate_runtime_begin_usec);
if (s->refreshed_mask > 0) {
_cleanup_strv_free_ char **l = NULL;
@@ -4358,6 +4429,8 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
} else if (streq(key, "reload-begin-usec"))
(void) deserialize_usec(value, &s->reload_begin_usec);
else if (streq(key, "revalidate-runtime-begin-usec"))
(void) deserialize_usec(value, &s->revalidate_runtime_begin_usec);
else if (streq(key, "refreshed-mask")) {
r = service_refresh_on_reload_from_string_many(value, &s->refreshed_mask);
if (r < 0)
@@ -4593,6 +4666,7 @@ static void service_notify_cgroup_empty_event(Unit *u) {
break;
case SERVICE_RUNNING:
case SERVICE_RUNNING_REVALIDATING:
/* service_enter_running() will figure out what to do */
service_enter_running(s, SERVICE_SUCCESS);
break;
@@ -4654,6 +4728,7 @@ static void service_notify_cgroup_oom_event(Unit *u, bool managed_oom) {
case SERVICE_EXITED:
case SERVICE_RUNNING:
case SERVICE_RUNNING_REVALIDATING:
if (s->oom_policy == OOM_STOP)
service_enter_stop(s, SERVICE_FAILURE_OOM_KILL);
else if (s->oom_policy == OOM_KILL)
@@ -4818,6 +4893,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
_fallthrough_;
case SERVICE_RUNNING:
case SERVICE_RUNNING_REVALIDATING:
service_enter_running(s, f);
break;
@@ -5440,7 +5516,7 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
if (strv_contains(tags, "STOPPING=1")) {
s->notify_state = NOTIFY_STOPPING;
if (IN_SET(s->state, SERVICE_RUNNING,
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY))
service_enter_stop_by_notify(s);
@@ -5477,7 +5553,7 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
/* Valid Type=notify-reload protocol? Then we're all good. */
service_enter_reload_post(s);
else if (s->state == SERVICE_RUNNING) {
else if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING)) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
/* Propagate a reload explicitly for plain RELOADING=1 (semantically equivalent to
@@ -5518,6 +5594,8 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
* don't need reload propagation nor do we want to restart the timeout. */
service_set_state(s, SERVICE_RELOAD_NOTIFY);
/* The reload will still be preserved by service_enter_running() replaying it once the bus
* name returns. */
if (s->state == SERVICE_RUNNING)
service_enter_reload_by_notify(s);
}
@@ -5552,7 +5630,7 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
@@ -5696,7 +5774,7 @@ static void service_notify_message(
}
/* Interpret RESTART_RESET=1 */
if (strv_contains(tags, "RESTART_RESET=1") && IN_SET(s->state, SERVICE_RUNNING, SERVICE_STOP)) {
if (strv_contains(tags, "RESTART_RESET=1") && IN_SET(s->state, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING, SERVICE_STOP)) {
log_unit_struct(u, LOG_NOTICE,
LOG_UNIT_MESSAGE(u, "Got RESTART_RESET=1, resetting restart counter from %u.", s->n_restarts),
LOG_ITEM("N_RESTARTS=0"),
@@ -5871,7 +5949,19 @@ static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, s
return 1;
}
static void service_bus_name_owner_change(Unit *u, const char *new_owner) {
static int service_dispatch_bus_name_grace(sd_event_source *source, usec_t usec, void *userdata) {
Service *s = ASSERT_PTR(SERVICE(userdata));
assert(source == s->bus_name_grace_event_source);
assert(s->state == SERVICE_RUNNING_REVALIDATING);
log_unit_warning(UNIT(s), "D-Bus name %s still not owned after grace period, giving up.", s->bus_name);
service_enter_exited_or_stop(s);
return 0;
}
static void service_bus_name_owner_change(Unit *u, const char *new_owner, bool from_signal) {
Service *s = ASSERT_PTR(SERVICE(u));
int r;
@@ -5883,10 +5973,12 @@ static void service_bus_name_owner_change(Unit *u, const char *new_owner) {
s->bus_name_good = new_owner;
if (s->type == SERVICE_DBUS) {
/* service_enter_running() will figure out what to do */
if (s->state == SERVICE_RUNNING)
service_enter_running(s, SERVICE_SUCCESS);
else if (s->state == SERVICE_START && new_owner)
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RUNNING_REVALIDATING)) {
if (!new_owner && from_signal)
service_enter_exited_or_stop(s);
else
service_enter_running(s, SERVICE_SUCCESS);
} else if (s->state == SERVICE_START && new_owner)
service_enter_start_post(s);
} else if (new_owner && pick_up_pid_from_bus_name(s)) {
@@ -6009,6 +6101,7 @@ static bool service_needs_console(Unit *u) {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RUNNING_REVALIDATING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
+2
View File
@@ -163,6 +163,7 @@ typedef struct Service {
bool timeout_abort_set;
usec_t runtime_max_usec;
usec_t runtime_rand_extra_usec;
usec_t revalidate_runtime_begin_usec;
ServiceTimeoutFailureMode timeout_start_failure_mode;
ServiceTimeoutFailureMode timeout_stop_failure_mode;
@@ -226,6 +227,7 @@ typedef struct Service {
bool exec_fd_hot:1;
char *bus_name;
sd_event_source *bus_name_grace_event_source;
char *status_text;
char *status_bus_error;
+6 -3
View File
@@ -3593,7 +3593,7 @@ static int signal_name_owner_changed_install_handler(sd_bus_message *message, vo
u->get_name_owner_slot = sd_bus_slot_unref(u->get_name_owner_slot);
if (UNIT_VTABLE(u)->bus_name_owner_change)
UNIT_VTABLE(u)->bus_name_owner_change(u, NULL);
UNIT_VTABLE(u)->bus_name_owner_change(u, NULL, /* from_signal= */ true);
return 0;
}
@@ -3612,7 +3612,7 @@ static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd
}
if (UNIT_VTABLE(u)->bus_name_owner_change)
UNIT_VTABLE(u)->bus_name_owner_change(u, empty_to_null(new_owner));
UNIT_VTABLE(u)->bus_name_owner_change(u, empty_to_null(new_owner), /* from_signal= */ true);
return 0;
}
@@ -3645,8 +3645,11 @@ static int get_name_owner_handler(sd_bus_message *message, void *userdata, sd_bu
assert(!isempty(new_owner));
}
/* Given we're in a GetNameOwner() refresh, which we issue right after (re)connecting to the broker,
* a NULL owner here may simply mean the owning client is still getting its bearings, so treat it as
* non-definitive and worth waiting a bit for. */
if (UNIT_VTABLE(u)->bus_name_owner_change)
UNIT_VTABLE(u)->bus_name_owner_change(u, new_owner);
UNIT_VTABLE(u)->bus_name_owner_change(u, new_owner, /* from_signal= */ false);
return 0;
}
+1 -1
View File
@@ -670,7 +670,7 @@ typedef struct UnitVTable {
void (*notify_pidref)(Unit *u, PidRef *parent_pidref, PidRef *child_pidref);
/* Called whenever a name this Unit registered for comes or goes away. */
void (*bus_name_owner_change)(Unit *u, const char *new_owner);
void (*bus_name_owner_change)(Unit *u, const char *new_owner, bool from_signal);
/* Called for each property that is being set */
int (*bus_set_property)(Unit *u, const char *name, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *reterr_error);
+4
View File
@@ -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',
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#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);
+161
View File
@@ -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"