From bc6377762c210d1bdd7fd2465930731d87dda576 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 29 Apr 2023 04:31:53 +0900 Subject: core/path: do not enqueue new job in .trigger_notify callback Otherwise, 1. X.path triggered X.service, and the service has waiting start job, 2. systemctl stop X.service 3. the waiting start job is cancelled to install new stop job, 4. path_trigger_notify() is called, and may reinstall new start job, 5. the stop job cannot be installed, and triggeres assertion. So, instead, let's add a defer event source, then enqueue the new start job after the stop (or any other type) job finished. Fixes https://github.com/systemd/systemd/issues/24577#issuecomment-1522628906. --- src/core/path.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/core/path.h | 2 ++ 2 files changed, 65 insertions(+), 5 deletions(-) (limited to 'src/core') diff --git a/src/core/path.c b/src/core/path.c index 9f6a246ab0..c95663c3aa 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -10,6 +10,7 @@ #include "dbus-path.h" #include "dbus-unit.h" #include "escape.h" +#include "event-util.h" #include "fd-util.h" #include "glob-util.h" #include "inotify-util.h" @@ -300,6 +301,7 @@ static void path_done(Unit *u) { assert(p); + p->trigger_notify_event_source = sd_event_source_disable_unref(p->trigger_notify_event_source); path_free_specs(p); } @@ -575,6 +577,9 @@ static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify) Unit *trigger; int r; + if (p->trigger_notify_event_source) + (void) event_source_disable(p->trigger_notify_event_source); + /* If the triggered unit is already running, so are we */ trigger = UNIT_TRIGGER(UNIT(p)); if (trigger && !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(trigger))) { @@ -799,8 +804,28 @@ fail: return 0; } -static void path_trigger_notify(Unit *u, Unit *other) { +static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer); + +static int path_trigger_notify_on_defer(sd_event_source *s, void *userdata) { + Path *p = ASSERT_PTR(userdata); + Unit *trigger; + + assert(s); + + trigger = UNIT_TRIGGER(UNIT(p)); + if (!trigger) { + log_unit_error(UNIT(p), "Unit to trigger vanished."); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return 0; + } + + path_trigger_notify_impl(UNIT(p), trigger, /* on_defer = */ true); + return 0; +} + +static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer) { Path *p = PATH(u); + int r; assert(u); assert(other); @@ -826,13 +851,46 @@ static void path_trigger_notify(Unit *u, Unit *other) { if (p->state == PATH_RUNNING && UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(p), "Got notified about unit deactivation."); - path_enter_waiting(p, false, true); + if (!on_defer) + log_unit_debug(u, "Got notified about unit deactivation."); } else if (p->state == PATH_WAITING && !UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(p), "Got notified about unit activation."); - path_enter_waiting(p, false, true); + if (!on_defer) + log_unit_debug(u, "Got notified about unit activation."); + } else + return; + + if (on_defer) { + path_enter_waiting(p, /* initial = */ false, /* from_trigger_notify = */ true); + return; } + + /* Do not call path_enter_waiting() directly from path_trigger_notify(), as this may be called by + * job_install() -> job_finish_and_invalidate() -> unit_trigger_notify(), and path_enter_waiting() + * may install another job and will trigger assertion in job_install(). + * https://github.com/systemd/systemd/issues/24577#issuecomment-1522628906 + * Hence, first setup defer event source here, and call path_enter_waiting() slightly later. */ + if (p->trigger_notify_event_source) { + r = sd_event_source_set_enabled(p->trigger_notify_event_source, SD_EVENT_ONESHOT); + if (r < 0) { + log_unit_warning_errno(u, r, "Failed to enable event source for triggering notify: %m"); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return; + } + } else { + r = sd_event_add_defer(u->manager->event, &p->trigger_notify_event_source, path_trigger_notify_on_defer, p); + if (r < 0) { + log_unit_warning_errno(u, r, "Failed to allocate event source for triggering notify: %m"); + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return; + } + + (void) sd_event_source_set_description(p->trigger_notify_event_source, "path-trigger-notify"); + } +} + +static void path_trigger_notify(Unit *u, Unit *other) { + path_trigger_notify_impl(u, other, /* on_defer = */ false); } static void path_reset_failed(Unit *u) { diff --git a/src/core/path.h b/src/core/path.h index c76103cc12..cb5b662911 100644 --- a/src/core/path.h +++ b/src/core/path.h @@ -65,6 +65,8 @@ struct Path { PathResult result; RateLimit trigger_limit; + + sd_event_source *trigger_notify_event_source; }; struct ActivationDetailsPath { -- cgit v1.2.1