]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add a new unit file setting CollectMode= for tweaking the GC logic
authorLennart Poettering <lennart@poettering.net>
Mon, 13 Nov 2017 16:14:07 +0000 (17:14 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 16 Nov 2017 13:38:36 +0000 (14:38 +0100)
Right now, the option only takes one of two possible values "inactive"
or "inactive-or-failed", the former being the default, and exposing same
behaviour as the status quo ante. If set to "inactive-or-failed" units
may be collected by the GC logic when in the "failed" state too.

This logic should be a nicer alternative to using the "-" modifier for
ExecStart= and friends, as the exit data is collected and logged about
and only removed when the GC comes along. This should be useful in
particular for per-connection socket-activated services, as well as
"systemd-run" command lines that shall leave no artifacts in the
system.

I was thinking about whether to expose this as a boolean, but opted for
an enum instead, as I have the suspicion other tweaks like this might be
a added later on, in which case we extend this setting instead of having
to add yet another one.

Also, let's add some documentation for the GC logic.

man/systemd.unit.xml
src/core/dbus-unit.c
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/unit.c
src/core/unit.h
src/shared/bus-unit-util.c

index d6feaa1817c7e717b11270d1b230a00db9bcd672..72c66028f89417abc4ee144707026423098d729a 100644 (file)
     socket-based activation which make dependencies implicit,
     resulting in a both simpler and more flexible system.</para>
 
-
     <para>Optionally, units may be instantiated from a
     template file at runtime. This allows creation of
     multiple units from a single configuration file. If
     </para>
   </refsect1>
 
+  <refsect1>
+    <title>Unit Garbage Collection</title>
+
+    <para>The system and service manager loads a unit's configuration automatically when a unit is referenced for the
+    first time. It will automatically unload the unit configuration and state again when the unit is not needed anymore
+    ("garbage collection"). A unit may be referenced through a number of different mechanisms:</para>
+
+    <orderedlist>
+      <listitem><para>Another loaded unit references it with a dependency such as <varname>After=</varname>,
+      <varname>Wants=</varname>, …</para></listitem>
+
+      <listitem><para>The unit is currently starting, running, reloading or stopping.</para></listitem>
+
+      <listitem><para>The unit is currently in the <constant>failed</constant> state. (But see below.)</para></listitem>
+
+      <listitem><para>A job for the unit is pending.</para></listitem>
+
+      <listitem><para>The unit is pinned by an active IPC client program.</para></listitem>
+
+      <listitem><para>The unit is a special "perpetual" unit that is always active and loaded. Examples for perpetual
+      units are the root mount unit <filename>-.mount</filename> or the scope unit <filename>init.scope</filename> that
+      the service manager itself lives in.</para></listitem>
+
+      <listitem><para>The unit has running processes associated with it.</para></listitem>
+    </orderedlist>
+
+    <para>The garbage collection logic may be altered with the <varname>CollectMode=</varname> option, which allows
+    configuration whether automatic unloading of units that are in <constant>failed</constant> state is permissible,
+    see below.</para>
+
+    <para>Note that when a unit's configuration and state is unloaded, all execution results, such as exit codes, exit
+    signals, resource consumption and other statistics are lost, except for what is stored in the log subsystem.</para>
+
+    <para>Use <command>systemctl daemon-reload</command> or an equivalent command to reload unit configuration while
+    the unit is already loaded. In this case all configuration settings are flushed out and replaced with the new
+    configuration (which however might not be in effect immediately), however all runtime state is
+    saved/restored.</para>
+  </refsect1>
+
   <refsect1>
     <title>[Unit] Section Options</title>
 
         ones.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>CollectMode=</varname></term>
+
+        <listitem><para>Tweaks the "garbage collection" algorithm for this unit. Takes one of <option>inactive</option>
+        or <option>inactive-or-failed</option>. If set to <option>inactive</option> the unit will be unloaded if it is
+        in the <constant>inactive</constant> state and is not referenced by clients, jobs or other units — however it
+        is not unloaded if it is in the <constant>failed</constant> state. In <option>failed</option> mode, failed
+        units are not unloaded until the user invoked <command>systemctl reset-failed</command> on them to reset the
+        <constant>failed</constant> state, or an equivalent command. This behaviour is altered if this option is set to
+        <option>inactive-or-failed</option>: in this case the unit is unloaded even if the unit is in a
+        <constant>failed</constant> state, and thus an explicitly resetting of the <constant>failed</constant> state is
+        not necessary. Note that if this mode is used unit results (such as exit codes, exit signals, consumed
+        resources, …) are flushed out immediately after the unit completed, except for what is stored in the logging
+        subsystem. Defaults to <option>inactive</option>.</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>JobTimeoutSec=</varname></term>
         <term><varname>JobRunningTimeoutSec=</varname></term>
index 32fcb5ef1c266a33367b1bb639fef4447289261d..561cf453f654fa4ae29adc5d2e5af952fc7b762d 100644 (file)
@@ -37,6 +37,7 @@
 #include "strv.h"
 #include "user-util.h"
 
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_collect_mode, collect_mode, CollectMode);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode);
 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_emergency_action, emergency_action, EmergencyAction);
@@ -798,6 +799,7 @@ const sd_bus_vtable bus_unit_vtable[] = {
         SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("InvocationID", "ay", bus_property_get_id128, offsetof(Unit, invocation_id), 0),
+        SD_BUS_PROPERTY("CollectMode", "s", property_get_collect_mode, offsetof(Unit, collect_mode), 0),
 
         SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED),
@@ -1354,6 +1356,25 @@ static int bus_unit_set_transient_property(
 
                 return 1;
 
+        } else if (streq(name, "CollectMode")) {
+                const char *s;
+                CollectMode m;
+
+                r = sd_bus_message_read(message, "s", &s);
+                if (r < 0)
+                        return r;
+
+                m = collect_mode_from_string(s);
+                if (m < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown garbage collection mode: %s", s);
+
+                if (mode != UNIT_CHECK) {
+                        u->collect_mode = m;
+                        unit_write_drop_in_format(u, mode, name, "[Unit]\nCollectMode=%s", collect_mode_to_string(m));
+                }
+
+                return 1;
+
         } else if (streq(name, "Slice")) {
                 Unit *slice;
                 const char *s;
index 5b73f9aa9b31ba5b0dd52de28377f92dcc96d1cd..ffc3e203598fe729753e129996e0ee19f1c781f6 100644 (file)
@@ -264,6 +264,7 @@ Unit.AssertACPower,              config_parse_unit_condition_string, CONDITION_A
 Unit.AssertUser,                 config_parse_unit_condition_string, CONDITION_USER,                offsetof(Unit, asserts)
 Unit.AssertGroup,                config_parse_unit_condition_string, CONDITION_GROUP,               offsetof(Unit, asserts)
 Unit.AssertNull,                 config_parse_unit_condition_null,   0,                             offsetof(Unit, asserts)
+Unit.CollectMode,                config_parse_collect_mode,          0,                             offsetof(Unit, collect_mode)
 m4_dnl
 Service.PIDFile,                 config_parse_unit_path_printf,      0,                             offsetof(Service, pid_file)
 Service.ExecStartPre,            config_parse_exec,                  SERVICE_EXEC_START_PRE,        offsetof(Service, exec_command)
index 34fc04b65a4cbb130b492a8faf1eb222de008677..c1cf8379e8286c7d6e350822759da00aa7423e32 100644 (file)
@@ -103,6 +103,8 @@ int config_parse_warn_compat(
         return 0;
 }
 
+DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode, "Failed to parse garbage collection mode");
+
 int config_parse_unit_deps(
                 const char *unit,
                 const char *filename,
index b0a3ce2c67f9c75d77d4cd17c5cc74e58b42d359..fbf2de23eb43194ff3c03fa72c5bfc65ce9b7c73 100644 (file)
@@ -122,6 +122,7 @@ int config_parse_exec_keyring_mode(const char *unit, const char *filename, unsig
 int config_parse_job_timeout_sec(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);
 int config_parse_job_running_timeout_sec(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);
 int config_parse_log_extra_fields(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);
+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);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 5c8dc4347ab31132b3c65919744c952595f711c7..25cdc045061f03a9644dc04718991d654738f073 100644 (file)
@@ -56,6 +56,7 @@
 #include "special.h"
 #include "stat-util.h"
 #include "stdio-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "umask-util.h"
@@ -75,7 +76,7 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
         [UNIT_TIMER] = &timer_vtable,
         [UNIT_PATH] = &path_vtable,
         [UNIT_SLICE] = &slice_vtable,
-        [UNIT_SCOPE] = &scope_vtable
+        [UNIT_SCOPE] = &scope_vtable,
 };
 
 static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency);
@@ -331,8 +332,12 @@ int unit_set_description(Unit *u, const char *description) {
 
 bool unit_check_gc(Unit *u) {
         UnitActiveState state;
+
         assert(u);
 
+        /* Checks whether the unit is ready to be unloaded for garbage collection. Returns true, when the unit shall
+         * stay around, false if there's no reason to keep it loaded. */
+
         if (u->job)
                 return true;
 
@@ -346,10 +351,6 @@ bool unit_check_gc(Unit *u) {
             UNIT_VTABLE(u)->release_resources)
                 UNIT_VTABLE(u)->release_resources(u);
 
-        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
-        if (state != UNIT_INACTIVE)
-                return true;
-
         if (u->perpetual)
                 return true;
 
@@ -359,6 +360,25 @@ bool unit_check_gc(Unit *u) {
         if (sd_bus_track_count(u->bus_track) > 0)
                 return true;
 
+        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
+        switch (u->collect_mode) {
+
+        case COLLECT_INACTIVE:
+                if (state != UNIT_INACTIVE)
+                        return true;
+
+                break;
+
+        case COLLECT_INACTIVE_OR_FAILED:
+                if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED))
+                        return true;
+
+                break;
+
+        default:
+                assert_not_reached("Unknown garbage collection mode");
+        }
+
         if (UNIT_VTABLE(u)->check_gc)
                 if (UNIT_VTABLE(u)->check_gc(u))
                         return true;
@@ -1083,6 +1103,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 "%s\tNeed Daemon Reload: %s\n"
                 "%s\tTransient: %s\n"
                 "%s\tPerpetual: %s\n"
+                "%s\tGarbage Collection Mode: %s\n"
                 "%s\tSlice: %s\n"
                 "%s\tCGroup: %s\n"
                 "%s\tCGroup realized: %s\n",
@@ -1100,6 +1121,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, yes_no(unit_need_daemon_reload(u)),
                 prefix, yes_no(u->transient),
                 prefix, yes_no(u->perpetual),
+                prefix, collect_mode_to_string(u->collect_mode),
                 prefix, strna(unit_slice_name(u)),
                 prefix, strna(u->cgroup_path),
                 prefix, yes_no(u->cgroup_realized));
@@ -5121,3 +5143,10 @@ void unit_unlink_state_files(Unit *u) {
                 u->exported_log_extra_fields = false;
         }
 }
+
+static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
+        [COLLECT_INACTIVE] = "inactive",
+        [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(collect_mode, CollectMode);
index b0df341a03a7e4631d1f0b428e7fdc1bb44f283f..03dd88dcca2b9cbe749da57dda4401d3cef4d3a6 100644 (file)
@@ -45,6 +45,13 @@ typedef enum KillOperation {
         _KILL_OPERATION_INVALID = -1
 } KillOperation;
 
+typedef enum CollectMode {
+        COLLECT_INACTIVE,
+        COLLECT_INACTIVE_OR_FAILED,
+        _COLLECT_MODE_MAX,
+        _COLLECT_MODE_INVALID = -1,
+} CollectMode;
+
 static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) {
         return IN_SET(t, UNIT_ACTIVE, UNIT_RELOADING);
 }
@@ -282,6 +289,9 @@ struct Unit {
         /* How to start OnFailure units */
         JobMode on_failure_job_mode;
 
+        /* Tweaking the GC logic */
+        CollectMode collect_mode;
+
         /* The current invocation ID */
         sd_id128_t invocation_id;
         char invocation_id_string[SD_ID128_STRING_MAX]; /* useful when logging */
@@ -773,3 +783,6 @@ void unit_unlink_state_files(Unit *u);
 #define LOG_UNIT_MESSAGE(unit, fmt, ...) "MESSAGE=%s: " fmt, (unit)->id, ##__VA_ARGS__
 #define LOG_UNIT_ID(unit) (unit)->manager->unit_log_format_string, (unit)->id
 #define LOG_UNIT_INVOCATION_ID(unit) (unit)->manager->invocation_log_format_string, (unit)->invocation_id_string
+
+const char* collect_mode_to_string(CollectMode m) _const_;
+CollectMode collect_mode_from_string(const char *s) _pure_;
index e24c0d4e1c81cf87ae8b43ed25a8900c2bd6e5d5..2b2480c2e13707c464330346b52117f9ae06368f 100644 (file)
@@ -371,7 +371,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
                               "RootDirectory", "SyslogIdentifier", "ProtectSystem",
                               "ProtectHome", "SELinuxContext", "Restart", "RootImage",
                               "NotifyAccess", "RuntimeDirectoryPreserve", "Personality",
-                              "KeyringMode"))
+                              "KeyringMode", "CollectMode"))
                 r = sd_bus_message_append(m, "v", "s", eq);
 
         else if (STR_IN_SET(field, "AppArmorProfile", "SmackProcessLabel")) {