]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/path: do not enqueue new job in .trigger_notify callback
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 28 Apr 2023 19:31:53 +0000 (04:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 28 Apr 2023 23:54:29 +0000 (08:54 +0900)
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
src/core/path.h

index 9f6a246ab0bc530cd398a87ef2f4697697c046ae..c95663c3aaed76d20c27c7e9da56a83685922570 100644 (file)
@@ -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) {
index c76103cc12944d1ece2896f000d3e3fd79595e7e..cb5b6629110bf4a43df1e2ef3671bff85bb81a09 100644 (file)
@@ -65,6 +65,8 @@ struct Path {
         PathResult result;
 
         RateLimit trigger_limit;
+
+        sd_event_source *trigger_notify_event_source;
 };
 
 struct ActivationDetailsPath {