c2dfb7
From 5458256264c97eee521caf07c705f549a0f0bd55 Mon Sep 17 00:00:00 2001
c2dfb7
From: Lennart Poettering <lennart@poettering.net>
c2dfb7
Date: Thu, 9 Aug 2018 16:26:27 +0200
c2dfb7
Subject: [PATCH] core: rework StopWhenUnneeded= logic
c2dfb7
c2dfb7
Previously, we'd act immediately on StopWhenUnneeded= when a unit state
c2dfb7
changes. With this rework we'll maintain a queue instead: whenever
c2dfb7
there's the chance that StopWhenUneeded= might have an effect we enqueue
c2dfb7
the unit, and process it later when we have nothing better to do.
c2dfb7
c2dfb7
This should make the implementation a bit more reliable, as the unit notify event
c2dfb7
cannot immediately enqueue tons of side-effect jobs that might
c2dfb7
contradict each other, but we do so only in a strictly ordered fashion,
c2dfb7
from the main event loop.
c2dfb7
c2dfb7
This slightly changes the check when to consider a unit "unneeded".
c2dfb7
Previously, we'd assume that a unit in "deactivating" state could also
c2dfb7
be cleaned up. With this new logic we'll only consider units unneeded
c2dfb7
that are fully up and have no job queued. This means that whenever
c2dfb7
there's something pending for a unit we won't clean it up.
c2dfb7
c2dfb7
(cherry picked from commit a3c1168ac293f16d9343d248795bb4c246aaff4a)
c2dfb7
c2dfb7
Resolves: #1798046
c2dfb7
---
c2dfb7
 src/core/manager.c |  43 ++++++++++++++++
c2dfb7
 src/core/manager.h |   3 ++
c2dfb7
 src/core/unit.c    | 122 +++++++++++++++++++++++++--------------------
c2dfb7
 src/core/unit.h    |   7 +++
c2dfb7
 4 files changed, 120 insertions(+), 55 deletions(-)
c2dfb7
c2dfb7
diff --git a/src/core/manager.c b/src/core/manager.c
c2dfb7
index 0eae7d46fb..4c04896aaa 100644
c2dfb7
--- a/src/core/manager.c
c2dfb7
+++ b/src/core/manager.c
c2dfb7
@@ -1211,6 +1211,45 @@ static unsigned manager_dispatch_gc_job_queue(Manager *m) {
c2dfb7
         return n;
c2dfb7
 }
c2dfb7
 
c2dfb7
+static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
c2dfb7
+        unsigned n = 0;
c2dfb7
+        Unit *u;
c2dfb7
+        int r;
c2dfb7
+
c2dfb7
+        assert(m);
c2dfb7
+
c2dfb7
+        while ((u = m->stop_when_unneeded_queue)) {
c2dfb7
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
c2dfb7
+                assert(m->stop_when_unneeded_queue);
c2dfb7
+
c2dfb7
+                assert(u->in_stop_when_unneeded_queue);
c2dfb7
+                LIST_REMOVE(stop_when_unneeded_queue, m->stop_when_unneeded_queue, u);
c2dfb7
+                u->in_stop_when_unneeded_queue = false;
c2dfb7
+
c2dfb7
+                n++;
c2dfb7
+
c2dfb7
+                if (!unit_is_unneeded(u))
c2dfb7
+                        continue;
c2dfb7
+
c2dfb7
+                log_unit_debug(u, "Unit is not needed anymore.");
c2dfb7
+
c2dfb7
+                /* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
c2dfb7
+                 * service being unnecessary after a while. */
c2dfb7
+
c2dfb7
+                if (!ratelimit_below(&u->auto_stop_ratelimit)) {
c2dfb7
+                        log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
c2dfb7
+                        continue;
c2dfb7
+                }
c2dfb7
+
c2dfb7
+                /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
c2dfb7
+                r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
c2dfb7
+                if (r < 0)
c2dfb7
+                        log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
c2dfb7
+        }
c2dfb7
+
c2dfb7
+        return n;
c2dfb7
+}
c2dfb7
+
c2dfb7
 static void manager_clear_jobs_and_units(Manager *m) {
c2dfb7
         Unit *u;
c2dfb7
 
c2dfb7
@@ -1228,6 +1267,7 @@ static void manager_clear_jobs_and_units(Manager *m) {
c2dfb7
         assert(!m->cleanup_queue);
c2dfb7
         assert(!m->gc_unit_queue);
c2dfb7
         assert(!m->gc_job_queue);
c2dfb7
+        assert(!m->stop_when_unneeded_queue);
c2dfb7
 
c2dfb7
         assert(hashmap_isempty(m->jobs));
c2dfb7
         assert(hashmap_isempty(m->units));
c2dfb7
@@ -2824,6 +2864,9 @@ int manager_loop(Manager *m) {
c2dfb7
                 if (manager_dispatch_cgroup_realize_queue(m) > 0)
c2dfb7
                         continue;
c2dfb7
 
c2dfb7
+                if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
c2dfb7
+                        continue;
c2dfb7
+
c2dfb7
                 if (manager_dispatch_dbus_queue(m) > 0)
c2dfb7
                         continue;
c2dfb7
 
c2dfb7
diff --git a/src/core/manager.h b/src/core/manager.h
c2dfb7
index fa47952d24..40568d3c8b 100644
c2dfb7
--- a/src/core/manager.h
c2dfb7
+++ b/src/core/manager.h
c2dfb7
@@ -130,6 +130,9 @@ struct Manager {
c2dfb7
         /* Target units whose default target dependencies haven't been set yet */
c2dfb7
         LIST_HEAD(Unit, target_deps_queue);
c2dfb7
 
c2dfb7
+        /* Units that might be subject to StopWhenUnneeded= clean-up */
c2dfb7
+        LIST_HEAD(Unit, stop_when_unneeded_queue);
c2dfb7
+
c2dfb7
         sd_event *event;
c2dfb7
 
c2dfb7
         /* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in
c2dfb7
diff --git a/src/core/unit.c b/src/core/unit.c
c2dfb7
index e1f5e6f7bd..40f138d25c 100644
c2dfb7
--- a/src/core/unit.c
c2dfb7
+++ b/src/core/unit.c
c2dfb7
@@ -438,6 +438,22 @@ void unit_add_to_dbus_queue(Unit *u) {
c2dfb7
         u->in_dbus_queue = true;
c2dfb7
 }
c2dfb7
 
c2dfb7
+void unit_add_to_stop_when_unneeded_queue(Unit *u) {
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        if (u->in_stop_when_unneeded_queue)
c2dfb7
+                return;
c2dfb7
+
c2dfb7
+        if (!u->stop_when_unneeded)
c2dfb7
+                return;
c2dfb7
+
c2dfb7
+        if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
c2dfb7
+                return;
c2dfb7
+
c2dfb7
+        LIST_PREPEND(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
c2dfb7
+        u->in_stop_when_unneeded_queue = true;
c2dfb7
+}
c2dfb7
+
c2dfb7
 static void bidi_set_free(Unit *u, Hashmap *h) {
c2dfb7
         Unit *other;
c2dfb7
         Iterator i;
c2dfb7
@@ -634,6 +650,9 @@ void unit_free(Unit *u) {
c2dfb7
         if (u->in_target_deps_queue)
c2dfb7
                 LIST_REMOVE(target_deps_queue, u->manager->target_deps_queue, u);
c2dfb7
 
c2dfb7
+        if (u->in_stop_when_unneeded_queue)
c2dfb7
+                LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
c2dfb7
+
c2dfb7
         safe_close(u->ip_accounting_ingress_map_fd);
c2dfb7
         safe_close(u->ip_accounting_egress_map_fd);
c2dfb7
 
c2dfb7
@@ -1950,55 +1969,71 @@ bool unit_can_reload(Unit *u) {
c2dfb7
         return UNIT_VTABLE(u)->reload;
c2dfb7
 }
c2dfb7
 
c2dfb7
-static void unit_check_unneeded(Unit *u) {
c2dfb7
-
c2dfb7
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
c2dfb7
-
c2dfb7
-        static const UnitDependency needed_dependencies[] = {
c2dfb7
+bool unit_is_unneeded(Unit *u) {
c2dfb7
+        static const UnitDependency deps[] = {
c2dfb7
                 UNIT_REQUIRED_BY,
c2dfb7
                 UNIT_REQUISITE_OF,
c2dfb7
                 UNIT_WANTED_BY,
c2dfb7
                 UNIT_BOUND_BY,
c2dfb7
         };
c2dfb7
-
c2dfb7
-        unsigned j;
c2dfb7
-        int r;
c2dfb7
+        size_t j;
c2dfb7
 
c2dfb7
         assert(u);
c2dfb7
 
c2dfb7
-        /* If this service shall be shut down when unneeded then do
c2dfb7
-         * so. */
c2dfb7
-
c2dfb7
         if (!u->stop_when_unneeded)
c2dfb7
-                return;
c2dfb7
+                return false;
c2dfb7
 
c2dfb7
-        if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
c2dfb7
-                return;
c2dfb7
+        /* Don't clean up while the unit is transitioning or is even inactive. */
c2dfb7
+        if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
c2dfb7
+                return false;
c2dfb7
+        if (u->job)
c2dfb7
+                return false;
c2dfb7
 
c2dfb7
-        for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) {
c2dfb7
+        for (j = 0; j < ELEMENTSOF(deps); j++) {
c2dfb7
                 Unit *other;
c2dfb7
                 Iterator i;
c2dfb7
                 void *v;
c2dfb7
 
c2dfb7
-                HASHMAP_FOREACH_KEY(v, other, u->dependencies[needed_dependencies[j]], i)
c2dfb7
-                        if (unit_active_or_pending(other) || unit_will_restart(other))
c2dfb7
-                                return;
c2dfb7
-        }
c2dfb7
+                /* If a dependending unit has a job queued, or is active (or in transitioning), or is marked for
c2dfb7
+                 * restart, then don't clean this one up. */
c2dfb7
 
c2dfb7
-        /* If stopping a unit fails continuously we might enter a stop
c2dfb7
-         * loop here, hence stop acting on the service being
c2dfb7
-         * unnecessary after a while. */
c2dfb7
-        if (!ratelimit_below(&u->auto_stop_ratelimit)) {
c2dfb7
-                log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
c2dfb7
-                return;
c2dfb7
+                HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i) {
c2dfb7
+                        if (u->job)
c2dfb7
+                                return false;
c2dfb7
+
c2dfb7
+                        if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other)))
c2dfb7
+                                return false;
c2dfb7
+
c2dfb7
+                        if (unit_will_restart(other))
c2dfb7
+                                return false;
c2dfb7
+                }
c2dfb7
         }
c2dfb7
 
c2dfb7
-        log_unit_info(u, "Unit not needed anymore. Stopping.");
c2dfb7
+        return true;
c2dfb7
+}
c2dfb7
 
c2dfb7
-        /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
c2dfb7
-        r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
c2dfb7
-        if (r < 0)
c2dfb7
-                log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
c2dfb7
+static void check_unneeded_dependencies(Unit *u) {
c2dfb7
+
c2dfb7
+        static const UnitDependency deps[] = {
c2dfb7
+                UNIT_REQUIRES,
c2dfb7
+                UNIT_REQUISITE,
c2dfb7
+                UNIT_WANTS,
c2dfb7
+                UNIT_BINDS_TO,
c2dfb7
+        };
c2dfb7
+        size_t j;
c2dfb7
+
c2dfb7
+        assert(u);
c2dfb7
+
c2dfb7
+        /* Add all units this unit depends on to the queue that processes StopWhenUnneeded= behaviour. */
c2dfb7
+
c2dfb7
+        for (j = 0; j < ELEMENTSOF(deps); j++) {
c2dfb7
+                Unit *other;
c2dfb7
+                Iterator i;
c2dfb7
+                void *v;
c2dfb7
+
c2dfb7
+                HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i)
c2dfb7
+                        unit_add_to_stop_when_unneeded_queue(other);
c2dfb7
+        }
c2dfb7
 }
c2dfb7
 
c2dfb7
 static void unit_check_binds_to(Unit *u) {
c2dfb7
@@ -2098,29 +2133,6 @@ static void retroactively_stop_dependencies(Unit *u) {
c2dfb7
                         manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL);
c2dfb7
 }
c2dfb7
 
c2dfb7
-static void check_unneeded_dependencies(Unit *u) {
c2dfb7
-        Unit *other;
c2dfb7
-        Iterator i;
c2dfb7
-        void *v;
c2dfb7
-
c2dfb7
-        assert(u);
c2dfb7
-        assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
c2dfb7
-
c2dfb7
-        /* Garbage collect services that might not be needed anymore, if enabled */
c2dfb7
-        HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i)
c2dfb7
-                if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
c2dfb7
-                        unit_check_unneeded(other);
c2dfb7
-        HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i)
c2dfb7
-                if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
c2dfb7
-                        unit_check_unneeded(other);
c2dfb7
-        HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUISITE], i)
c2dfb7
-                if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
c2dfb7
-                        unit_check_unneeded(other);
c2dfb7
-        HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i)
c2dfb7
-                if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
c2dfb7
-                        unit_check_unneeded(other);
c2dfb7
-}
c2dfb7
-
c2dfb7
 void unit_start_on_failure(Unit *u) {
c2dfb7
         Unit *other;
c2dfb7
         Iterator i;
c2dfb7
@@ -2423,7 +2435,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
c2dfb7
                 }
c2dfb7
 
c2dfb7
                 /* stop unneeded units regardless if going down was expected or not */
c2dfb7
-                if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns))
c2dfb7
+                if (UNIT_IS_INACTIVE_OR_FAILED(ns))
c2dfb7
                         check_unneeded_dependencies(u);
c2dfb7
 
c2dfb7
                 if (ns != os && ns == UNIT_FAILED) {
c2dfb7
@@ -2483,7 +2495,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
c2dfb7
 
c2dfb7
         if (!MANAGER_IS_RELOADING(u->manager)) {
c2dfb7
                 /* Maybe we finished startup and are now ready for being stopped because unneeded? */
c2dfb7
-                unit_check_unneeded(u);
c2dfb7
+                unit_add_to_stop_when_unneeded_queue(u);
c2dfb7
 
c2dfb7
                 /* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens when
c2dfb7
                  * something BindsTo= to a Type=oneshot unit, as these units go directly from starting to inactive,
c2dfb7
diff --git a/src/core/unit.h b/src/core/unit.h
c2dfb7
index 99755823eb..595ee88d43 100644
c2dfb7
--- a/src/core/unit.h
c2dfb7
+++ b/src/core/unit.h
c2dfb7
@@ -212,6 +212,9 @@ typedef struct Unit {
c2dfb7
         /* Target dependencies queue */
c2dfb7
         LIST_FIELDS(Unit, target_deps_queue);
c2dfb7
 
c2dfb7
+        /* Queue of units with StopWhenUnneeded set that shell be checked for clean-up. */
c2dfb7
+        LIST_FIELDS(Unit, stop_when_unneeded_queue);
c2dfb7
+
c2dfb7
         /* PIDs we keep an eye on. Note that a unit might have many
c2dfb7
          * more, but these are the ones we care enough about to
c2dfb7
          * process SIGCHLD for */
c2dfb7
@@ -322,6 +325,7 @@ typedef struct Unit {
c2dfb7
         bool in_cgroup_realize_queue:1;
c2dfb7
         bool in_cgroup_empty_queue:1;
c2dfb7
         bool in_target_deps_queue:1;
c2dfb7
+        bool in_stop_when_unneeded_queue:1;
c2dfb7
 
c2dfb7
         bool sent_dbus_new_signal:1;
c2dfb7
 
c2dfb7
@@ -615,6 +619,7 @@ void unit_add_to_dbus_queue(Unit *u);
c2dfb7
 void unit_add_to_cleanup_queue(Unit *u);
c2dfb7
 void unit_add_to_gc_queue(Unit *u);
c2dfb7
 void unit_add_to_target_deps_queue(Unit *u);
c2dfb7
+void unit_add_to_stop_when_unneeded_queue(Unit *u);
c2dfb7
 
c2dfb7
 int unit_merge(Unit *u, Unit *other);
c2dfb7
 int unit_merge_by_name(Unit *u, const char *other);
c2dfb7
@@ -751,6 +756,8 @@ bool unit_type_supported(UnitType t);
c2dfb7
 
c2dfb7
 bool unit_is_pristine(Unit *u);
c2dfb7
 
c2dfb7
+bool unit_is_unneeded(Unit *u);
c2dfb7
+
c2dfb7
 pid_t unit_control_pid(Unit *u);
c2dfb7
 pid_t unit_main_pid(Unit *u);
c2dfb7