job: Don't mark as redundant if deps are relevant

In the steps given in #13850, the resulting graph looks like:

    C (Anchor) -> B -> A

Since B is inactive, it will be flagged as redundant and removed from
the transaction, causing A to get garbage collected. The proposed fix is
to not mark nodes as redundant if doing so causes a relevant node to be
garbage collected.

Fixes #13850
This commit is contained in:
Kevin Kuehler 2019-11-19 13:43:58 -08:00 committed by Lennart Poettering
parent dc5737470e
commit 097537f07a
3 changed files with 50 additions and 12 deletions

View file

@ -383,25 +383,62 @@ JobType job_type_lookup_merge(JobType a, JobType b) {
return job_merging_table[(a - 1) * a / 2 + b];
}
bool job_type_is_redundant(JobType a, UnitActiveState b) {
switch (a) {
bool job_later_link_matters(Job *j, JobType type, unsigned generation) {
JobDependency *l;
assert(j);
j->generation = generation;
LIST_FOREACH(subject, l, j->subject_list) {
UnitActiveState state = _UNIT_ACTIVE_STATE_INVALID;
/* Have we seen this before? */
if (l->object->generation == generation)
continue;
state = unit_active_state(l->object->unit);
switch (type) {
case JOB_START:
return IN_SET(state, UNIT_INACTIVE, UNIT_FAILED) ||
job_later_link_matters(l->object, type, generation);
case JOB_STOP:
return IN_SET(state, UNIT_ACTIVE, UNIT_RELOADING) ||
job_later_link_matters(l->object, type, generation);
default:
assert_not_reached("Invalid job type");
}
}
return false;
}
bool job_is_redundant(Job *j, unsigned generation) {
assert(j);
UnitActiveState state = unit_active_state(j->unit);
switch (j->type) {
case JOB_START:
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING);
return IN_SET(state, UNIT_ACTIVE, UNIT_RELOADING) && !job_later_link_matters(j, JOB_START, generation);
case JOB_STOP:
return IN_SET(b, UNIT_INACTIVE, UNIT_FAILED);
return IN_SET(state, UNIT_INACTIVE, UNIT_FAILED) && !job_later_link_matters(j, JOB_STOP, generation);
case JOB_VERIFY_ACTIVE:
return IN_SET(b, UNIT_ACTIVE, UNIT_RELOADING);
return IN_SET(state, UNIT_ACTIVE, UNIT_RELOADING);
case JOB_RELOAD:
return
b == UNIT_RELOADING;
state == UNIT_RELOADING;
case JOB_RESTART:
return
b == UNIT_ACTIVATING;
state == UNIT_ACTIVATING;
case JOB_NOP:
return true;

View file

@ -196,7 +196,8 @@ _pure_ static inline bool job_type_is_superset(JobType a, JobType b) {
return a == job_type_lookup_merge(a, b);
}
bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_;
bool job_later_link_matters(Job *j, JobType type, unsigned generation);
bool job_is_redundant(Job *j, unsigned generation);
/* Collapses a state-dependent job type into a simpler type by observing
* the state of the unit which it is going to be applied to. */

View file

@ -279,7 +279,7 @@ static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) {
return 0;
}
static void transaction_drop_redundant(Transaction *tr) {
static void transaction_drop_redundant(Transaction *tr, unsigned generation) {
bool again;
/* Goes through the transaction and removes all jobs of the units whose jobs are all noops. If not
@ -299,7 +299,7 @@ static void transaction_drop_redundant(Transaction *tr) {
LIST_FOREACH(transaction, k, j)
if (tr->anchor_job == k ||
!job_type_is_redundant(k->type, unit_active_state(k->unit)) ||
!job_is_redundant(k, generation) ||
(k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) {
keep = true;
break;
@ -730,7 +730,7 @@ int transaction_activate(
transaction_minimize_impact(tr);
/* Third step: Drop redundant jobs */
transaction_drop_redundant(tr);
transaction_drop_redundant(tr, generation++);
for (;;) {
/* Fourth step: Let's remove unneeded jobs that might
@ -772,7 +772,7 @@ int transaction_activate(
}
/* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
transaction_drop_redundant(tr);
transaction_drop_redundant(tr, generation++);
/* Ninth step: check whether we can actually apply this */
r = transaction_is_destructive(tr, mode, e);