diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index d6feaa1817..72c66028f8 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -213,7 +213,6 @@
socket-based activation which make dependencies implicit,
resulting in a both simpler and more flexible system.
-
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 @@
+
+ Unit Garbage Collection
+
+ 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:
+
+
+ Another loaded unit references it with a dependency such as After=,
+ Wants=, …
+
+ The unit is currently starting, running, reloading or stopping.
+
+ The unit is currently in the failed state. (But see below.)
+
+ A job for the unit is pending.
+
+ The unit is pinned by an active IPC client program.
+
+ The unit is a special "perpetual" unit that is always active and loaded. Examples for perpetual
+ units are the root mount unit -.mount or the scope unit init.scope that
+ the service manager itself lives in.
+
+ The unit has running processes associated with it.
+
+
+ The garbage collection logic may be altered with the CollectMode= option, which allows
+ configuration whether automatic unloading of units that are in failed state is permissible,
+ see below.
+
+ 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.
+
+ Use systemctl daemon-reload 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.
+
+
[Unit] Section Options
@@ -747,6 +785,23 @@
ones.
+
+ CollectMode=
+
+ Tweaks the "garbage collection" algorithm for this unit. Takes one of
+ or . If set to the unit will be unloaded if it is
+ in the inactive state and is not referenced by clients, jobs or other units — however it
+ is not unloaded if it is in the failed state. In mode, failed
+ units are not unloaded until the user invoked systemctl reset-failed on them to reset the
+ failed state, or an equivalent command. This behaviour is altered if this option is set to
+ : in this case the unit is unloaded even if the unit is in a
+ failed state, and thus an explicitly resetting of the failed 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 .
+
+
+
JobTimeoutSec=JobRunningTimeoutSec=
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 32fcb5ef1c..561cf453f6 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -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;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 5b73f9aa9b..ffc3e20359 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -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)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 34fc04b65a..c1cf8379e8 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -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,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index b0a3ce2c67..fbf2de23eb 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -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);
diff --git a/src/core/unit.c b/src/core/unit.c
index 5c8dc4347a..25cdc04506 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -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);
diff --git a/src/core/unit.h b/src/core/unit.h
index b0df341a03..03dd88dcca 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -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_;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index e24c0d4e1c..2b2480c2e1 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -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")) {