7e7c9f
From 909d27fffffb28f4da7752be25e5c47990eba641 Mon Sep 17 00:00:00 2001
7e7c9f
From: Lennart Poettering <lennart@poettering.net>
7e7c9f
Date: Mon, 13 Nov 2017 17:14:07 +0100
7e7c9f
Subject: [PATCH] core: add a new unit file setting CollectMode= for tweaking
7e7c9f
 the GC logic
7e7c9f
7e7c9f
Right now, the option only takes one of two possible values "inactive"
7e7c9f
or "inactive-or-failed", the former being the default, and exposing same
7e7c9f
behaviour as the status quo ante. If set to "inactive-or-failed" units
7e7c9f
may be collected by the GC logic when in the "failed" state too.
7e7c9f
7e7c9f
This logic should be a nicer alternative to using the "-" modifier for
7e7c9f
ExecStart= and friends, as the exit data is collected and logged about
7e7c9f
and only removed when the GC comes along. This should be useful in
7e7c9f
particular for per-connection socket-activated services, as well as
7e7c9f
"systemd-run" command lines that shall leave no artifacts in the
7e7c9f
system.
7e7c9f
7e7c9f
I was thinking about whether to expose this as a boolean, but opted for
7e7c9f
an enum instead, as I have the suspicion other tweaks like this might be
7e7c9f
a added later on, in which case we extend this setting instead of having
7e7c9f
to add yet another one.
7e7c9f
7e7c9f
Also, let's add some documentation for the GC logic.
7e7c9f
7e7c9f
(cherry-picked from commit 5afe510c89f26b0e721b276a0e78af914b47f0b0)
7e7c9f
7e7c9f
Resolves: #1817576
7e7c9f
---
7e7c9f
 man/systemd.unit.xml                  | 56 +++++++++++++++++++++++++++
7e7c9f
 src/core/dbus-unit.c                  | 20 ++++++++++
7e7c9f
 src/core/load-fragment-gperf.gperf.m4 |  1 +
7e7c9f
 src/core/load-fragment.c              |  2 +
7e7c9f
 src/core/load-fragment.h              |  1 +
7e7c9f
 src/core/unit.c                       | 36 ++++++++++++++---
7e7c9f
 src/core/unit.h                       | 13 +++++++
7e7c9f
 7 files changed, 124 insertions(+), 5 deletions(-)
7e7c9f
7e7c9f
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
7e7c9f
index 414749bae9..0c06add166 100644
7e7c9f
--- a/man/systemd.unit.xml
7e7c9f
+++ b/man/systemd.unit.xml
7e7c9f
@@ -303,6 +303,45 @@
7e7c9f
     </para>
7e7c9f
   </refsect1>
7e7c9f
 
7e7c9f
+  <refsect1>
7e7c9f
+    <title>Unit Garbage Collection</title>
7e7c9f
+
7e7c9f
+    <para>The system and service manager loads a unit's configuration automatically when a unit is referenced for the
7e7c9f
+    first time. It will automatically unload the unit configuration and state again when the unit is not needed anymore
7e7c9f
+    ("garbage collection"). A unit may be referenced through a number of different mechanisms:</para>
7e7c9f
+
7e7c9f
+    <orderedlist>
7e7c9f
+      <listitem><para>Another loaded unit references it with a dependency such as <varname>After=</varname>,
7e7c9f
+      <varname>Wants=</varname>, …</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>The unit is currently starting, running, reloading or stopping.</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>The unit is currently in the <constant>failed</constant> state. (But see below.)</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>A job for the unit is pending.</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>The unit is pinned by an active IPC client program.</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>The unit is a special "perpetual" unit that is always active and loaded. Examples for perpetual
7e7c9f
+      units are the root mount unit <filename>-.mount</filename> or the scope unit <filename>init.scope</filename> that
7e7c9f
+      the service manager itself lives in.</para></listitem>
7e7c9f
+
7e7c9f
+      <listitem><para>The unit has running processes associated with it.</para></listitem>
7e7c9f
+    </orderedlist>
7e7c9f
+
7e7c9f
+    <para>The garbage collection logic may be altered with the <varname>CollectMode=</varname> option, which allows
7e7c9f
+    configuration whether automatic unloading of units that are in <constant>failed</constant> state is permissible,
7e7c9f
+    see below.</para>
7e7c9f
+
7e7c9f
+    <para>Note that when a unit's configuration and state is unloaded, all execution results, such as exit codes, exit
7e7c9f
+    signals, resource consumption and other statistics are lost, except for what is stored in the log subsystem.</para>
7e7c9f
+
7e7c9f
+    <para>Use <command>systemctl daemon-reload</command> or an equivalent command to reload unit configuration while
7e7c9f
+    the unit is already loaded. In this case all configuration settings are flushed out and replaced with the new
7e7c9f
+    configuration (which however might not be in effect immediately), however all runtime state is
7e7c9f
+    saved/restored.</para>
7e7c9f
+  </refsect1>
7e7c9f
+
7e7c9f
   <refsect1>
7e7c9f
     <title>[Unit] Section Options</title>
7e7c9f
 
7e7c9f
@@ -666,6 +705,23 @@
7e7c9f
         ones.</para></listitem>
7e7c9f
       </varlistentry>
7e7c9f
 
7e7c9f
+      <varlistentry>
7e7c9f
+        <term><varname>CollectMode=</varname></term>
7e7c9f
+
7e7c9f
+        <listitem><para>Tweaks the "garbage collection" algorithm for this unit. Takes one of <option>inactive</option>
7e7c9f
+        or <option>inactive-or-failed</option>. If set to <option>inactive</option> the unit will be unloaded if it is
7e7c9f
+        in the <constant>inactive</constant> state and is not referenced by clients, jobs or other units — however it
7e7c9f
+        is not unloaded if it is in the <constant>failed</constant> state. In <option>failed</option> mode, failed
7e7c9f
+        units are not unloaded until the user invoked <command>systemctl reset-failed</command> on them to reset the
7e7c9f
+        <constant>failed</constant> state, or an equivalent command. This behaviour is altered if this option is set to
7e7c9f
+        <option>inactive-or-failed</option>: in this case the unit is unloaded even if the unit is in a
7e7c9f
+        <constant>failed</constant> state, and thus an explicitly resetting of the <constant>failed</constant> state is
7e7c9f
+        not necessary. Note that if this mode is used unit results (such as exit codes, exit signals, consumed
7e7c9f
+        resources, …) are flushed out immediately after the unit completed, except for what is stored in the logging
7e7c9f
+        subsystem. Defaults to <option>inactive</option>.</para>
7e7c9f
+        </listitem>
7e7c9f
+      </varlistentry>
7e7c9f
+
7e7c9f
       <varlistentry>
7e7c9f
         <term><varname>JobTimeoutSec=</varname></term>
7e7c9f
         <term><varname>JobTimeoutAction=</varname></term>
7e7c9f
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
7e7c9f
index 77073308c8..ea6ac6767f 100644
7e7c9f
--- a/src/core/dbus-unit.c
7e7c9f
+++ b/src/core/dbus-unit.c
7e7c9f
@@ -34,6 +34,7 @@
7e7c9f
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState);
7e7c9f
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode);
7e7c9f
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
7e7c9f
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_collect_mode, collect_mode, CollectMode);
7e7c9f
 
7e7c9f
 static int property_get_names(
7e7c9f
                 sd_bus *bus,
7e7c9f
@@ -605,6 +606,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
7e7c9f
         SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0),
7e7c9f
         SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST),
7e7c9f
         SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST),
7e7c9f
+        SD_BUS_PROPERTY("CollectMode", "s", property_get_collect_mode, offsetof(Unit, collect_mode), 0),
7e7c9f
 
7e7c9f
         SD_BUS_METHOD("Start", "s", "o", method_start, 0),
7e7c9f
         SD_BUS_METHOD("Stop", "s", "o", method_stop, 0),
7e7c9f
@@ -937,6 +939,24 @@ static int bus_unit_set_transient_property(
7e7c9f
 
7e7c9f
                 return 1;
7e7c9f
 
7e7c9f
+        } else if (streq(name, "CollectMode")) {
7e7c9f
+                const char *s;
7e7c9f
+                CollectMode m;
7e7c9f
+
7e7c9f
+                r = sd_bus_message_read(message, "s", &s);
7e7c9f
+                if (r < 0)
7e7c9f
+                        return r;
7e7c9f
+
7e7c9f
+                m = collect_mode_from_string(s);
7e7c9f
+                if (m < 0)
7e7c9f
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown garbage collection mode: %s", s);
7e7c9f
+
7e7c9f
+                if (mode != UNIT_CHECK) {
7e7c9f
+                        u->collect_mode = m;
7e7c9f
+                        unit_write_drop_in_format(u, mode, name, "[Unit]\nCollectMode=%s", collect_mode_to_string(m));
7e7c9f
+                }
7e7c9f
+
7e7c9f
+                return 1;
7e7c9f
         } else if (streq(name, "Slice") && unit_get_cgroup_context(u)) {
7e7c9f
                 const char *s;
7e7c9f
 
7e7c9f
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
7e7c9f
index 664bba0ef6..3a8ee96fa3 100644
7e7c9f
--- a/src/core/load-fragment-gperf.gperf.m4
7e7c9f
+++ b/src/core/load-fragment-gperf.gperf.m4
7e7c9f
@@ -200,6 +200,7 @@ Unit.AssertCapability,           config_parse_unit_condition_string, CONDITION_C
7e7c9f
 Unit.AssertHost,                 config_parse_unit_condition_string, CONDITION_HOST,                offsetof(Unit, asserts)
7e7c9f
 Unit.AssertACPower,              config_parse_unit_condition_string, CONDITION_AC_POWER,            offsetof(Unit, asserts)
7e7c9f
 Unit.AssertNull,                 config_parse_unit_condition_null,   0,                             offsetof(Unit, asserts)
7e7c9f
+Unit.CollectMode,                config_parse_collect_mode,          0,                             offsetof(Unit, collect_mode)
7e7c9f
 m4_dnl
7e7c9f
 Service.PIDFile,                 config_parse_unit_path_printf,      0,                             offsetof(Service, pid_file)
7e7c9f
 Service.ExecStartPre,            config_parse_exec,                  SERVICE_EXEC_START_PRE,        offsetof(Service, exec_command)
7e7c9f
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
7e7c9f
index 1721fea8f3..8d73d5df41 100644
7e7c9f
--- a/src/core/load-fragment.c
7e7c9f
+++ b/src/core/load-fragment.c
7e7c9f
@@ -98,6 +98,8 @@ int config_parse_warn_compat(
7e7c9f
         return 0;
7e7c9f
 }
7e7c9f
 
7e7c9f
+DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode, "Failed to parse garbage collection mode");
7e7c9f
+
7e7c9f
 int config_parse_unit_deps(const char *unit,
7e7c9f
                            const char *filename,
7e7c9f
                            unsigned line,
7e7c9f
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
7e7c9f
index 4bd286c11b..7b1193ddb2 100644
7e7c9f
--- a/src/core/load-fragment.h
7e7c9f
+++ b/src/core/load-fragment.h
7e7c9f
@@ -110,6 +110,7 @@ int config_parse_cpu_quota(const char *unit, const char *filename, unsigned line
7e7c9f
 int config_parse_protect_home(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
7e7c9f
 int config_parse_protect_system(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
7e7c9f
 int config_parse_bus_name(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
7e7c9f
+int config_parse_collect_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
7e7c9f
 
7e7c9f
 /* gperf prototypes */
7e7c9f
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
7e7c9f
diff --git a/src/core/unit.c b/src/core/unit.c
7e7c9f
index eff9fdbe70..502830d2cb 100644
7e7c9f
--- a/src/core/unit.c
7e7c9f
+++ b/src/core/unit.c
7e7c9f
@@ -67,7 +67,7 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
7e7c9f
         [UNIT_TIMER] = &timer_vtable,
7e7c9f
         [UNIT_PATH] = &path_vtable,
7e7c9f
         [UNIT_SLICE] = &slice_vtable,
7e7c9f
-        [UNIT_SCOPE] = &scope_vtable
7e7c9f
+        [UNIT_SCOPE] = &scope_vtable,
7e7c9f
 };
7e7c9f
 
7e7c9f
 static int maybe_warn_about_dependency(const char *id, const char *other, UnitDependency dependency);
7e7c9f
@@ -285,6 +285,7 @@ int unit_set_description(Unit *u, const char *description) {
7e7c9f
 
7e7c9f
 bool unit_may_gc(Unit *u) {
7e7c9f
         UnitActiveState state;
7e7c9f
+
7e7c9f
         assert(u);
7e7c9f
 
7e7c9f
         /* Checks whether the unit is ready to be unloaded for garbage collection.
7e7c9f
@@ -308,16 +309,31 @@ bool unit_may_gc(Unit *u) {
7e7c9f
             UNIT_VTABLE(u)->release_resources)
7e7c9f
                 UNIT_VTABLE(u)->release_resources(u);
7e7c9f
 
7e7c9f
-        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
7e7c9f
-        if (state != UNIT_INACTIVE)
7e7c9f
-                return false;
7e7c9f
-
7e7c9f
         if (UNIT_VTABLE(u)->no_gc)
7e7c9f
                 return false;
7e7c9f
 
7e7c9f
         if (u->no_gc)
7e7c9f
                 return false;
7e7c9f
 
7e7c9f
+        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
7e7c9f
+        switch (u->collect_mode) {
7e7c9f
+
7e7c9f
+        case COLLECT_INACTIVE:
7e7c9f
+                if (state != UNIT_INACTIVE)
7e7c9f
+                        return false;
7e7c9f
+
7e7c9f
+                break;
7e7c9f
+
7e7c9f
+        case COLLECT_INACTIVE_OR_FAILED:
7e7c9f
+                if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
7e7c9f
+                        return false;
7e7c9f
+
7e7c9f
+                break;
7e7c9f
+
7e7c9f
+        default:
7e7c9f
+                assert_not_reached("Unknown garbage collection mode");
7e7c9f
+        }
7e7c9f
+
7e7c9f
         if (UNIT_VTABLE(u)->may_gc && !UNIT_VTABLE(u)->may_gc(u))
7e7c9f
                 return false;
7e7c9f
 
7e7c9f
@@ -896,6 +912,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
7e7c9f
                 "%s\tMay GC: %s\n"
7e7c9f
                 "%s\tNeed Daemon Reload: %s\n"
7e7c9f
                 "%s\tTransient: %s\n"
7e7c9f
+                "%s\tGarbage Collection Mode: %s\n"
7e7c9f
                 "%s\tSlice: %s\n"
7e7c9f
                 "%s\tCGroup: %s\n"
7e7c9f
                 "%s\tCGroup realized: %s\n"
7e7c9f
@@ -913,6 +930,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
7e7c9f
                 prefix, yes_no(unit_may_gc(u)),
7e7c9f
                 prefix, yes_no(unit_need_daemon_reload(u)),
7e7c9f
                 prefix, yes_no(u->transient),
7e7c9f
+                prefix, collect_mode_to_string(u->collect_mode),
7e7c9f
                 prefix, strna(unit_slice_name(u)),
7e7c9f
                 prefix, strna(u->cgroup_path),
7e7c9f
                 prefix, yes_no(u->cgroup_realized),
7e7c9f
@@ -3732,3 +3750,11 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
7e7c9f
 };
7e7c9f
 
7e7c9f
 DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
7e7c9f
+
7e7c9f
+static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
7e7c9f
+        [COLLECT_INACTIVE] = "inactive",
7e7c9f
+        [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
7e7c9f
+};
7e7c9f
+
7e7c9f
+DEFINE_STRING_TABLE_LOOKUP(collect_mode, CollectMode);
7e7c9f
+
7e7c9f
diff --git a/src/core/unit.h b/src/core/unit.h
7e7c9f
index 3b0fd8d9df..8cc7a9e0b2 100644
7e7c9f
--- a/src/core/unit.h
7e7c9f
+++ b/src/core/unit.h
7e7c9f
@@ -60,6 +60,13 @@ typedef enum KillOperation {
7e7c9f
         KILL_ABORT,
7e7c9f
 } KillOperation;
7e7c9f
 
7e7c9f
+typedef enum CollectMode {
7e7c9f
+        COLLECT_INACTIVE,
7e7c9f
+        COLLECT_INACTIVE_OR_FAILED,
7e7c9f
+        _COLLECT_MODE_MAX,
7e7c9f
+        _COLLECT_MODE_INVALID = -1,
7e7c9f
+} CollectMode;
7e7c9f
+
7e7c9f
 static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) {
7e7c9f
         return t == UNIT_ACTIVE || t == UNIT_RELOADING;
7e7c9f
 }
7e7c9f
@@ -198,6 +205,9 @@ struct Unit {
7e7c9f
         /* How to start OnFailure units */
7e7c9f
         JobMode on_failure_job_mode;
7e7c9f
 
7e7c9f
+        /* Tweaking the GC logic */
7e7c9f
+        CollectMode collect_mode;
7e7c9f
+
7e7c9f
         /* Garbage collect us we nobody wants or requires us anymore */
7e7c9f
         bool stop_when_unneeded;
7e7c9f
 
7e7c9f
@@ -630,6 +640,9 @@ pid_t unit_main_pid(Unit *u);
7e7c9f
 const char *unit_active_state_to_string(UnitActiveState i) _const_;
7e7c9f
 UnitActiveState unit_active_state_from_string(const char *s) _pure_;
7e7c9f
 
7e7c9f
+const char* collect_mode_to_string(CollectMode m) _const_;
7e7c9f
+CollectMode collect_mode_from_string(const char *s) _pure_;
7e7c9f
+
7e7c9f
 bool unit_needs_console(Unit *u);
7e7c9f
 
7e7c9f
 /* Macros which append UNIT= or USER_UNIT= to the message */