core: add a new unit file setting CollectMode= for tweaking the GC logic

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.
This commit is contained in:
Lennart Poettering 2017-11-13 17:14:07 +01:00
parent 7eb2a8a125
commit 5afe510c89
8 changed files with 129 additions and 7 deletions

View File

@ -213,7 +213,6 @@
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
@ -417,6 +416,45 @@
</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>
@ -747,6 +785,23 @@
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>

View 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;

View 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)

View 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,

View 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);

View 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);

View 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_;

View 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")) {