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