transaction_delete_job(tr, other, true);
}
-_pure_ static bool job_is_conflicted_by(Job *j) {
+static bool job_is_conflicted_by(Job *j) {
assert(j);
/* Returns true if this job is pulled in by a least one
} while (again);
}
-_pure_ static bool unit_matters_to_anchor(Unit *u, Job *job) {
- assert(u);
+static bool job_matters_to_anchor(Job *job) {
assert(job);
assert(!job->transaction_prev);
- /* Checks whether at least one of the jobs for this unit
- * matters to the anchor. */
+ /* Checks whether at least one of the jobs for this transaction matters to the anchor. */
LIST_FOREACH(transaction, j, job)
if (j->matters_to_anchor)
_cleanup_free_ char *ans = NULL;
size_t size = 0;
+ assert(unit_log_field);
+
STRV_FOREACH_PAIR(unit_id, job_type, pairs) {
size_t next;
if (strv_push_pair(&array, k->unit->id, (char*) job_type_to_string(k->type)) < 0)
log_oom();
- if (!delete && hashmap_get(tr->jobs, k->unit) && !unit_matters_to_anchor(k->unit, k))
+ if (!delete && hashmap_contains(tr->jobs, k->unit) && !job_matters_to_anchor(k))
/* Ok, we can drop this one, so let's do so. */
delete = k;
if (j->unit->ignore_on_isolate)
continue;
- if (hashmap_get(tr->jobs, j->unit))
+ if (hashmap_contains(tr->jobs, j->unit))
continue;
/* Not invalidating recursively. Avoids triggering
/* Clean the job dependencies */
transaction_unlink_job(tr, j, false);
- installed_job = job_install(j);
+ /* When RestartMode=direct is used, the service being restarted don't enter the inactive/failed
+ * state, i.e. unit_process_job -> job_finish_and_invalidate is never called, and the previous
+ * job might still be running (especially for Type=oneshot services). We need to refuse
+ * late merge and re-enqueue the anchor job. */
+ installed_job = job_install(j,
+ /* refuse_late_merge = */ mode == JOB_RESTART_DEPENDENCIES && j == tr->anchor_job);
if (installed_job != j) {
/* j has been merged into a previously installed job */
if (tr->anchor_job == j)
tr->anchor_job = installed_job;
+
hashmap_remove_value(m->jobs, UINT32_TO_PTR(j->id), j);
- job_free(j);
- j = installed_job;
+ free_and_replace_full(j, installed_job, job_free);
}
job_add_to_run_queue(j);
}
}
-void transaction_add_propagate_reload_jobs(Transaction *tr, Unit *unit, Job *by, bool ignore_order, sd_bus_error *e) {
+void transaction_add_propagate_reload_jobs(
+ Transaction *tr,
+ Unit *unit,
+ Job *by,
+ TransactionAddFlags flags) {
+
JobType nt;
Unit *dep;
int r;
assert(unit);
UNIT_FOREACH_DEPENDENCY(dep, unit, UNIT_ATOM_PROPAGATES_RELOAD_TO) {
+ _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
+
nt = job_type_collapse(JOB_TRY_RELOAD, dep);
if (nt == JOB_NOP)
continue;
- r = transaction_add_job_and_dependencies(tr, nt, dep, by, false, false, false, ignore_order, e);
- if (r < 0) {
+ r = transaction_add_job_and_dependencies(tr, nt, dep, by, flags, &e);
+ if (r < 0)
log_unit_warning(dep,
"Cannot add dependency reload job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ bus_error_message(&e, r));
}
}
+static JobType job_type_propagate_stop_graceful(Job *j) {
+ JobType type;
+
+ if (!j)
+ return JOB_STOP;
+
+ type = JOB_STOP;
+
+ LIST_FOREACH(transaction, i, j)
+ switch (i->type) {
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ /* Nothing to worry about, an appropriate job is in-place */
+ return JOB_NOP;
+
+ case JOB_START:
+ /* This unit is pulled in by other dependency types in this transaction. We will run
+ * into job type conflict if we enqueue a stop job, so let's enqueue a restart job
+ * instead. */
+ type = JOB_RESTART;
+ break;
+
+ default: /* We don't care about others */
+ ;
+
+ }
+
+ return type;
+}
+
int transaction_add_job_and_dependencies(
Transaction *tr,
JobType type,
Unit *unit,
Job *by,
- bool matters,
- bool conflicts,
- bool ignore_requirements,
- bool ignore_order,
+ TransactionAddFlags flags,
sd_bus_error *e) {
bool is_new;
- Unit *dep;
Job *ret;
int r;
assert(type < _JOB_TYPE_MAX_IN_TRANSACTION);
assert(unit);
- /* Before adding jobs for this unit, let's ensure that its state has been loaded
- * This matters when jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()).
- * This way, we "recursively" coldplug units, ensuring that we do not look at state of
- * not-yet-coldplugged units. */
+ /* Before adding jobs for this unit, let's ensure that its state has been loaded This matters when
+ * jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()). This way, we
+ * "recursively" coldplug units, ensuring that we do not look at state of not-yet-coldplugged
+ * units. */
if (MANAGER_IS_RELOADING(unit->manager))
unit_coldplug(unit);
if (type != JOB_STOP) {
r = bus_unit_validate_load_state(unit, e);
- /* The time-based cache allows to start new units without daemon-reload,
- * but if they are already referenced (because of dependencies or ordering)
- * then we have to force a load of the fragment. As an optimization, check
- * first if anything in the usual paths was modified since the last time
- * the cache was loaded. Also check if the last time an attempt to load the
- * unit was made was before the most recent cache refresh, so that we know
- * we need to try again — even if the cache is current, it might have been
- * updated in a different context before we had a chance to retry loading
- * this particular unit.
+ /* The time-based cache allows to start new units without daemon-reload, but if they are
+ * already referenced (because of dependencies or ordering) then we have to force a load of
+ * the fragment. As an optimization, check first if anything in the usual paths was modified
+ * since the last time the cache was loaded. Also check if the last time an attempt to load
+ * the unit was made was before the most recent cache refresh, so that we know we need to try
+ * again — even if the cache is current, it might have been updated in a different context
+ * before we had a chance to retry loading this particular unit.
*
* Given building up the transaction is a synchronous operation, attempt
* to load the unit immediately. */
if (!ret)
return -ENOMEM;
- ret->ignore_order = ret->ignore_order || ignore_order;
+ if (FLAGS_SET(flags, TRANSACTION_IGNORE_ORDER))
+ ret->ignore_order = true;
/* Then, add a link to the job. */
if (by) {
- if (!job_dependency_new(by, ret, matters, conflicts))
+ if (!job_dependency_new(by, ret, FLAGS_SET(flags, TRANSACTION_MATTERS), FLAGS_SET(flags, TRANSACTION_CONFLICTS)))
return -ENOMEM;
} else {
/* If the job has no parent job, it is the anchor job. */
tr->anchor_job = ret;
}
- if (is_new && !ignore_requirements && type != JOB_NOP) {
- _cleanup_set_free_ Set *following = NULL;
+ if (!is_new || FLAGS_SET(flags, TRANSACTION_IGNORE_REQUIREMENTS) || type == JOB_NOP)
+ return 0;
- /* If we are following some other unit, make sure we
- * add all dependencies of everybody following. */
- if (unit_following_set(ret->unit, &following) > 0)
- SET_FOREACH(dep, following) {
- r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e);
- if (r < 0) {
- log_unit_full_errno(dep, r == -ERFKILL ? LOG_INFO : LOG_WARNING, r,
- "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ _cleanup_set_free_ Set *following = NULL;
+ Unit *dep;
+
+ /* If we are following some other unit, make sure we add all dependencies of everybody following. */
+ if (unit_following_set(ret->unit, &following) > 0)
+ SET_FOREACH(dep, following) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ log_unit_full_errno(dep, r == -ERFKILL ? LOG_INFO : LOG_WARNING, r,
+ "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
+ }
- /* Finally, recursively add in all dependencies. */
- if (IN_SET(type, JOB_START, JOB_RESTART)) {
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) {
- r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- return r;
+ /* Finally, recursively add in all dependencies. */
+ if (IN_SET(type, JOB_START, JOB_RESTART)) {
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START_IGNORED) {
- r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e);
- if (r < 0) {
- /* unit masked, job type not applicable and unit not found are not considered as errors. */
- log_unit_full_errno(dep,
- IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING,
- r, "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START_IGNORED) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ /* unit masked, job type not applicable and unit not found are not considered
+ * as errors. */
+ log_unit_full_errno(dep,
+ IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING,
+ r, "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_VERIFY) {
- r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- return r;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_VERIFY) {
+ r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP) {
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- return r;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, TRANSACTION_MATTERS | TRANSACTION_CONFLICTS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP_IGNORED) {
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e);
- if (r < 0) {
- log_unit_warning(dep,
- "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP_IGNORED) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ log_unit_warning(dep,
+ "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
}
+ }
- if (IN_SET(type, JOB_STOP, JOB_RESTART)) {
- _cleanup_set_free_ Set *propagated_restart = NULL;
- /* We propagate STOP as STOP, but RESTART only as TRY_RESTART, in order not to start
- * dependencies that are not around. */
+ if (IN_SET(type, JOB_RESTART, JOB_STOP) || (type == JOB_START && FLAGS_SET(flags, TRANSACTION_PROPAGATE_START_AS_RESTART))) {
+ bool is_stop = type == JOB_STOP;
- if (type == JOB_RESTART)
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_RESTART) {
- JobType nt;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, is_stop ? UNIT_ATOM_PROPAGATE_STOP : UNIT_ATOM_PROPAGATE_RESTART) {
+ /* We propagate RESTART only as TRY_RESTART, in order not to start dependencies that
+ * are not around. */
+ JobType nt;
- r = set_ensure_put(&propagated_restart, NULL, dep);
- if (r < 0)
- return r;
+ nt = job_type_collapse(is_stop ? JOB_STOP : JOB_TRY_RESTART, dep);
+ if (nt == JOB_NOP)
+ continue;
- nt = job_type_collapse(JOB_TRY_RESTART, dep);
- if (nt == JOB_NOP)
- continue;
+ r = transaction_add_job_and_dependencies(tr, nt, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ return r;
- r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- return r;
+ sd_bus_error_free(e);
+ }
+ }
- sd_bus_error_free(e);
- }
- }
+ /* Process UNIT_ATOM_PROPAGATE_STOP_GRACEFUL (PropagatesStopTo=) units. We need to wait until
+ * all other dependencies are processed, i.e. we're the anchor job or already in the recursion
+ * that handles it. */
+ if (!by || FLAGS_SET(flags, TRANSACTION_PROCESS_PROPAGATE_STOP_GRACEFUL))
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_STOP_GRACEFUL) {
+ JobType nt;
+ Job *j;
+
+ j = hashmap_get(tr->jobs, dep);
+ nt = job_type_propagate_stop_graceful(j);
- /* The 'stop' part of a restart job is also propagated to
- * units with UNIT_ATOM_PROPAGATE_STOP */
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_STOP) {
- /* Units experienced restart propagation are skipped */
- if (set_contains(propagated_restart, dep))
+ if (nt == JOB_NOP)
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, false, false, ignore_order, e);
+ r = transaction_add_job_and_dependencies(tr, nt, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER) | TRANSACTION_PROCESS_PROPAGATE_STOP_GRACEFUL, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
return r;
sd_bus_error_free(e);
}
}
- }
+ }
- if (type == JOB_RELOAD)
- transaction_add_propagate_reload_jobs(tr, ret->unit, ret, ignore_order, e);
+ if (type == JOB_RELOAD)
+ transaction_add_propagate_reload_jobs(tr, ret->unit, ret, flags & TRANSACTION_IGNORE_ORDER);
- /* JOB_VERIFY_ACTIVE requires no dependency handling */
- }
+ /* JOB_VERIFY_ACTIVE requires no dependency handling */
return 0;
+
+fail:
+ /* Recursive call failed to add required jobs so let's drop top level job as well. */
+ log_unit_debug_errno(unit, r, "Cannot add dependency job to transaction, deleting job %s/%s again: %s",
+ unit->id, job_type_to_string(type), bus_error_message(e, r));
+
+ transaction_delete_job(tr, ret, /* delete_dependencies= */ false);
+ return r;
}
static bool shall_stop_on_isolate(Transaction *tr, Unit *u) {
return false;
/* Is there already something listed for this? */
- if (hashmap_get(tr->jobs, u))
+ if (hashmap_contains(tr->jobs, u))
return false;
return true;
assert(m);
HASHMAP_FOREACH_KEY(u, k, m->units) {
+ _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
Unit *o;
/* Ignore aliases */
if (keep)
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, NULL);
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, TRANSACTION_MATTERS, &e);
if (r < 0)
- log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %m");
+ log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %s", bus_error_message(&e, r));
}
return 0;
assert(u);
UNIT_FOREACH_DEPENDENCY(trigger, u, UNIT_ATOM_TRIGGERED_BY) {
+ _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
/* No need to stop inactive jobs */
if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(trigger)) && !trigger->job)
continue;
/* Is there already something listed for this? */
- if (hashmap_get(tr->jobs, trigger))
+ if (hashmap_contains(tr->jobs, trigger))
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, tr->anchor_job, true, false, false, false, NULL);
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, tr->anchor_job, TRANSACTION_MATTERS, &e);
if (r < 0)
- log_unit_warning_errno(u, r, "Cannot add triggered by job, ignoring: %m");
+ log_unit_warning_errno(u, r, "Cannot add triggered by job, ignoring: %s", bus_error_message(&e, r));
}
return 0;