Merge pull request #9853 from poettering/uneeded-queue

rework StopWhenUnneeded=1 logic
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-08-21 10:06:30 +02:00 committed by GitHub
commit 00c4361878
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 59 deletions

9
TODO
View file

@ -50,9 +50,12 @@ Features:
output of "systemctl list-units" slightly by showing the tree structure of
the slices, and the units attached to them.
* the stop-when-unneded feature should be reworked: there should be a queue of
units, and we should only enqeueu stop jobs from a defer event that processes
queue instead of right-away when we assume that a unit is now unneeded.
* the a-posteriori stopping of units bound to units that disappeared logic
should be reworked: there should be a queue of units, and we should only
enqeue stop jobs from a defer event that processes queue instead of
right-away when we find a unit that is bound to one that doesn't exist
anymore. (similar to how the stop-unneeded queue has been reworked the same
way)
* nspawn: make nspawn suitable for shell pipelines: instead of triggering a
hangup when input is finished, send ^D, which synthesizes an EOF. Then wait

View file

@ -1211,6 +1211,45 @@ static unsigned manager_dispatch_gc_job_queue(Manager *m) {
return n;
}
static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
unsigned n = 0;
Unit *u;
int r;
assert(m);
while ((u = m->stop_when_unneeded_queue)) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
assert(m->stop_when_unneeded_queue);
assert(u->in_stop_when_unneeded_queue);
LIST_REMOVE(stop_when_unneeded_queue, m->stop_when_unneeded_queue, u);
u->in_stop_when_unneeded_queue = false;
n++;
if (!unit_is_unneeded(u))
continue;
log_unit_debug(u, "Unit is not needed anymore.");
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
* service being unnecessary after a while. */
if (!ratelimit_below(&u->auto_stop_ratelimit)) {
log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
continue;
}
/* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
}
return n;
}
static void manager_clear_jobs_and_units(Manager *m) {
Unit *u;
@ -1228,6 +1267,7 @@ static void manager_clear_jobs_and_units(Manager *m) {
assert(!m->cleanup_queue);
assert(!m->gc_unit_queue);
assert(!m->gc_job_queue);
assert(!m->stop_when_unneeded_queue);
assert(hashmap_isempty(m->jobs));
assert(hashmap_isempty(m->units));
@ -2797,6 +2837,9 @@ int manager_loop(Manager *m) {
if (manager_dispatch_cgroup_realize_queue(m) > 0)
continue;
if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
continue;
if (manager_dispatch_dbus_queue(m) > 0)
continue;

View file

@ -158,6 +158,9 @@ struct Manager {
/* Target units whose default target dependencies haven't been set yet */
LIST_HEAD(Unit, target_deps_queue);
/* Units that might be subject to StopWhenUnneeded= clean-up */
LIST_HEAD(Unit, stop_when_unneeded_queue);
sd_event *event;
/* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in

View file

@ -438,6 +438,22 @@ void unit_add_to_dbus_queue(Unit *u) {
u->in_dbus_queue = true;
}
void unit_submit_to_stop_when_unneeded_queue(Unit *u) {
assert(u);
if (u->in_stop_when_unneeded_queue)
return;
if (!u->stop_when_unneeded)
return;
if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
return;
LIST_PREPEND(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
u->in_stop_when_unneeded_queue = true;
}
static void bidi_set_free(Unit *u, Hashmap *h) {
Unit *other;
Iterator i;
@ -634,6 +650,9 @@ void unit_free(Unit *u) {
if (u->in_target_deps_queue)
LIST_REMOVE(target_deps_queue, u->manager->target_deps_queue, u);
if (u->in_stop_when_unneeded_queue)
LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
safe_close(u->ip_accounting_ingress_map_fd);
safe_close(u->ip_accounting_egress_map_fd);
@ -1950,55 +1969,71 @@ bool unit_can_reload(Unit *u) {
return UNIT_VTABLE(u)->reload;
}
static void unit_check_unneeded(Unit *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
static const UnitDependency needed_dependencies[] = {
bool unit_is_unneeded(Unit *u) {
static const UnitDependency deps[] = {
UNIT_REQUIRED_BY,
UNIT_REQUISITE_OF,
UNIT_WANTED_BY,
UNIT_BOUND_BY,
};
unsigned j;
int r;
size_t j;
assert(u);
/* If this service shall be shut down when unneeded then do
* so. */
if (!u->stop_when_unneeded)
return;
return false;
if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)))
return;
/* Don't clean up while the unit is transitioning or is even inactive. */
if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
return false;
if (u->job)
return false;
for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) {
for (j = 0; j < ELEMENTSOF(deps); j++) {
Unit *other;
Iterator i;
void *v;
HASHMAP_FOREACH_KEY(v, other, u->dependencies[needed_dependencies[j]], i)
if (unit_active_or_pending(other) || unit_will_restart(other))
return;
/* If a dependent unit has a job queued, is active or transitioning, or is marked for
* restart, then don't clean this one up. */
HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i) {
if (u->job)
return false;
if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other)))
return false;
if (unit_will_restart(other))
return false;
}
}
/* If stopping a unit fails continuously we might enter a stop
* loop here, hence stop acting on the service being
* unnecessary after a while. */
if (!ratelimit_below(&u->auto_stop_ratelimit)) {
log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
return;
return true;
}
static void check_unneeded_dependencies(Unit *u) {
static const UnitDependency deps[] = {
UNIT_REQUIRES,
UNIT_REQUISITE,
UNIT_WANTS,
UNIT_BINDS_TO,
};
size_t j;
assert(u);
/* Add all units this unit depends on to the queue that processes StopWhenUnneeded= behaviour. */
for (j = 0; j < ELEMENTSOF(deps); j++) {
Unit *other;
Iterator i;
void *v;
HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]], i)
unit_submit_to_stop_when_unneeded_queue(other);
}
log_unit_info(u, "Unit not needed anymore. Stopping.");
/* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */
r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r));
}
static void unit_check_binds_to(Unit *u) {
@ -2098,29 +2133,6 @@ static void retroactively_stop_dependencies(Unit *u) {
manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL);
}
static void check_unneeded_dependencies(Unit *u) {
Unit *other;
Iterator i;
void *v;
assert(u);
assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u)));
/* Garbage collect services that might not be needed anymore, if enabled */
HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i)
if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
unit_check_unneeded(other);
HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i)
if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
unit_check_unneeded(other);
HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUISITE], i)
if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
unit_check_unneeded(other);
HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i)
if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other)))
unit_check_unneeded(other);
}
void unit_start_on_failure(Unit *u) {
Unit *other;
Iterator i;
@ -2423,7 +2435,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
}
/* stop unneeded units regardless if going down was expected or not */
if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns))
if (UNIT_IS_INACTIVE_OR_FAILED(ns))
check_unneeded_dependencies(u);
if (ns != os && ns == UNIT_FAILED) {
@ -2483,7 +2495,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
if (!MANAGER_IS_RELOADING(u->manager)) {
/* Maybe we finished startup and are now ready for being stopped because unneeded? */
unit_check_unneeded(u);
unit_submit_to_stop_when_unneeded_queue(u);
/* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens when
* something BindsTo= to a Type=oneshot unit, as these units go directly from starting to inactive,

View file

@ -212,6 +212,9 @@ typedef struct Unit {
/* Target dependencies queue */
LIST_FIELDS(Unit, target_deps_queue);
/* Queue of units with StopWhenUnneeded set that shell be checked for clean-up. */
LIST_FIELDS(Unit, stop_when_unneeded_queue);
/* PIDs we keep an eye on. Note that a unit might have many
* more, but these are the ones we care enough about to
* process SIGCHLD for */
@ -322,6 +325,7 @@ typedef struct Unit {
bool in_cgroup_realize_queue:1;
bool in_cgroup_empty_queue:1;
bool in_target_deps_queue:1;
bool in_stop_when_unneeded_queue:1;
bool sent_dbus_new_signal:1;
@ -613,6 +617,7 @@ void unit_add_to_dbus_queue(Unit *u);
void unit_add_to_cleanup_queue(Unit *u);
void unit_add_to_gc_queue(Unit *u);
void unit_add_to_target_deps_queue(Unit *u);
void unit_submit_to_stop_when_unneeded_queue(Unit *u);
int unit_merge(Unit *u, Unit *other);
int unit_merge_by_name(Unit *u, const char *other);
@ -749,6 +754,8 @@ bool unit_type_supported(UnitType t);
bool unit_is_pristine(Unit *u);
bool unit_is_unneeded(Unit *u);
pid_t unit_control_pid(Unit *u);
pid_t unit_main_pid(Unit *u);