manager: when two pending jobs conflict, keep the one that "conflicts", remove the one that is "conflicted"

This gives the writer of units control which unit is kept and which is
stopped when two units conflict.
This commit is contained in:
Lennart Poettering 2010-08-09 22:32:30 +02:00
parent 6e98720f14
commit 69dd2852bb
17 changed files with 161 additions and 41 deletions

2
fixme
View file

@ -1,7 +1,5 @@
* dot output for --test for 'initial description'
* conflicted-by: to have a defined winner for conflicts:
* check 'disable'
<Viking-Ice> "Warning: Unit file changed in disk, 'systemctl --system daemon-reload' recomended
<kay> when does it do that?

View file

@ -336,7 +336,22 @@
independent of and orthogonal to the
<varname>After=</varname> and
<varname>Before=</varname> ordering
dependencies.</para></listitem>
dependencies.</para>
<para>If a unit A that conflicts with
a unit B is scheduled to be started at
the same time as B, the transaction
will either fail (in case both are
required part of the transaction) or
be modified to be fixed (in case one
or both jobs are not a required part
of the transaction). In the latter
case the job that is not the required
will be removed, or in case both are
not required the unit that conflicts
will be started and the unit that is
conflicted is
stopped.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -166,9 +166,27 @@
<listitem><para>Ask for confirmation when spawning processes. This switch has no effect when run as session instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--show-status</option></term>
<term><option>--show-status=</option></term>
<listitem><para>Show terse service status information while booting. This switch has no effect when run as session instance.</para></listitem>
<listitem><para>Show terse service
status information while booting. This
switch has no effect when run as
session instance. Takes a boolean
argument which may be omitted
which is interpreted as
<option>true</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--sysv-console=</option></term>
<listitem><para>Controls whether
output of SysV init scripts will be
directed to the console. This switch
has no effect when run as session
instance. Takes a boolean argument
which may be omitted which is
interpreted as
<option>true</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--log-target=</option></term>
@ -805,18 +823,6 @@
units.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.log_target=</varname></term>
<term><varname>systemd.log_level=</varname></term>
<term><varname>systemd.log_color=</varname></term>
<term><varname>systemd.log_location=</varname></term>
<listitem><para>Controls log output,
with the same effect as the
<varname>$SYSTEMD_LOG_TARGET</varname>, <varname>$SYSTEMD_LOG_LEVEL</varname>, <varname>$SYSTEMD_LOG_COLOR</varname>, <varname>$SYSTEMD_LOG_LOCATION</varname>
environment variables described above.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.dump_core=</varname></term>
@ -852,6 +858,16 @@
<literal>-1</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.confirm_spawn=</varname></term>
<listitem><para>Takes a boolean
argument. If <option>true</option>
asks for confirmation when spawning
processes. Defaults to
<option>false</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.show_status=</varname></term>
@ -862,6 +878,32 @@
<option>true</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.sysv_console=</varname></term>
<listitem><para>Takes a boolean
argument. If <option>true</option>
output of SysV init scripts will be
directed to the console. Defaults to
<option>true</option>, unless
<option>quiet</option> is passed as
kernel command line option in which
case it defaults to
<option>false</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.log_target=</varname></term>
<term><varname>systemd.log_level=</varname></term>
<term><varname>systemd.log_color=</varname></term>
<term><varname>systemd.log_location=</varname></term>
<listitem><para>Controls log output,
with the same effect as the
<varname>$SYSTEMD_LOG_TARGET</varname>, <varname>$SYSTEMD_LOG_LEVEL</varname>, <varname>$SYSTEMD_LOG_COLOR</varname>, <varname>$SYSTEMD_LOG_LOCATION</varname>
environment variables described above.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>

View file

@ -156,7 +156,7 @@ static int automount_add_default_dependencies(Automount *a) {
if ((r = unit_add_dependency_by_name(UNIT(a), UNIT_AFTER, SPECIAL_FSCK_TARGET, NULL, true)) < 0)
return r;
if ((r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0)
if ((r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0)
return r;
}

View file

@ -70,6 +70,7 @@
" <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"WantedBy\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"Conflicts\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"ConflictedBy\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"Before\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"After\" type=\"as\" access=\"read\"/>\n" \
" <property name=\"OnFailure\" type=\"as\" access=\"read\"/>\n" \
@ -108,6 +109,7 @@
{ "org.freedesktop.systemd1.Unit", "RequiredByOverridable",bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_REQUIRED_BY_OVERRIDABLE] }, \
{ "org.freedesktop.systemd1.Unit", "WantedBy", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_WANTED_BY] }, \
{ "org.freedesktop.systemd1.Unit", "Conflicts", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_CONFLICTS] }, \
{ "org.freedesktop.systemd1.Unit", "ConflictedBy", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_CONFLICTED_BY] }, \
{ "org.freedesktop.systemd1.Unit", "Before", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_BEFORE] }, \
{ "org.freedesktop.systemd1.Unit", "After", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_AFTER] }, \
{ "org.freedesktop.systemd1.Unit", "OnFailure", bus_unit_append_dependencies, "as", u->meta.dependencies[UNIT_ON_FAILURE] }, \

View file

@ -93,7 +93,7 @@ void job_free(Job *j) {
free(j);
}
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) {
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
JobDependency *l;
assert(object);
@ -109,6 +109,7 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters) {
l->subject = subject;
l->object = object;
l->matters = matters;
l->conflicts = conflicts;
if (subject)
LIST_PREPEND(JobDependency, subject, subject->subject_list, l);
@ -533,6 +534,14 @@ int job_finish_and_invalidate(Job *j, bool success) {
other->meta.job->type == JOB_VERIFY_ACTIVE ||
other->meta.job->type == JOB_RELOAD_OR_START))
job_finish_and_invalidate(other->meta.job, false);
SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTED_BY], i)
if (!other->meta.ignore_dependency_failure &&
other->meta.job &&
(other->meta.job->type == JOB_START ||
other->meta.job->type == JOB_VERIFY_ACTIVE ||
other->meta.job->type == JOB_RELOAD_OR_START))
job_finish_and_invalidate(other->meta.job, false);
}
}

View file

@ -80,6 +80,7 @@ struct JobDependency {
LIST_FIELDS(JobDependency, object);
bool matters;
bool conflicts;
};
struct Job {
@ -121,7 +122,7 @@ Job* job_new(Manager *m, JobType type, Unit *unit);
void job_free(Job *job);
void job_dump(Job *j, FILE*f, const char *prefix);
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters);
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts);
void job_dependency_free(JobDependency *l);
void job_dependency_delete(Job *subject, Job *object, bool *matters);

View file

@ -715,6 +715,20 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job
other->object_list = NULL;
transaction_delete_job(m, other, true);
}
static bool job_is_conflicted_by(Job *j) {
JobDependency *l;
assert(j);
/* Returns true if this job is pulled in by a least one
* ConflictedBy dependency. */
LIST_FOREACH(object, l, j->object_list)
if (l->conflicts)
return true;
return false;
}
static int delete_one_unmergeable_job(Manager *m, Job *j) {
Job *k;
@ -738,7 +752,36 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
/* Ok, we found two that conflict, let's see if we can
* drop one of them */
if (!j->matters_to_anchor)
if (!j->matters_to_anchor && !k->matters_to_anchor) {
/* Both jobs don't matter, so let's
* find the one that is smarter to
* remove. Let's think positive and
* rather remove stops then starts --
* except if something is being
* stopped because it is conflicted by
* another unit in which case we
* rather remove the start. */
log_debug("Looking at job %s/%s conflicted_by=%s", j->unit->meta.id, job_type_to_string(j->type), yes_no(j->type == JOB_STOP && job_is_conflicted_by(j)));
log_debug("Looking at job %s/%s conflicted_by=%s", k->unit->meta.id, job_type_to_string(k->type), yes_no(k->type == JOB_STOP && job_is_conflicted_by(k)));
if (j->type == JOB_STOP) {
if (job_is_conflicted_by(j))
d = k;
else
d = j;
} else if (k->type == JOB_STOP) {
if (job_is_conflicted_by(k))
d = j;
else
d = k;
}
} else if (!j->matters_to_anchor)
d = j;
else if (!k->matters_to_anchor)
d = k;
@ -1324,6 +1367,7 @@ static int transaction_add_job_and_dependencies(
Job *by,
bool matters,
bool override,
bool conflicts,
DBusError *e,
Job **_ret) {
Job *ret;
@ -1352,46 +1396,50 @@ static int transaction_add_job_and_dependencies(
return -ENOMEM;
/* Then, add a link to the job. */
if (!job_dependency_new(by, ret, matters))
if (!job_dependency_new(by, ret, matters, conflicts))
return -ENOMEM;
if (is_new) {
/* Finally, recursively add in all dependencies. */
if (type == JOB_START || type == JOB_RELOAD_OR_START) {
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRES], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, e, NULL)) < 0 && r != -EBADR)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, override, false, e, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, e, NULL)) < 0 && r != -EBADR) {
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !override, override, false, e, NULL)) < 0 && r != -EBADR) {
log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, bus_error(e, r));
dbus_error_free(e);
}
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_WANTS], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, e, NULL)) < 0) {
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, false, false, e, NULL)) < 0) {
log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, bus_error(e, r));
dbus_error_free(e);
}
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUISITE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, e, NULL)) < 0 && r != -EBADR)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, override, false, e, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUISITE_OVERRIDABLE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, e, NULL)) < 0 && r != -EBADR) {
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !override, override, false, e, NULL)) < 0 && r != -EBADR) {
log_warning("Cannot add dependency job for unit %s, ignoring: %s", dep->meta.id, bus_error(e, r));
dbus_error_free(e);
}
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_CONFLICTS], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, e, NULL)) < 0 && r != -EBADR)
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, true, e, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_CONFLICTED_BY], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, override, false, e, NULL)) < 0 && r != -EBADR)
goto fail;
} else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
SET_FOREACH(dep, ret->unit->meta.dependencies[UNIT_REQUIRED_BY], i)
if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, e, NULL)) < 0 && r != -EBADR)
if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, override, false, e, NULL)) < 0 && r != -EBADR)
goto fail;
}
@ -1432,7 +1480,7 @@ static int transaction_add_isolate_jobs(Manager *m) {
if (hashmap_get(m->transaction_jobs, u))
continue;
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, NULL, NULL)) < 0)
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, u, NULL, true, false, false, NULL, NULL)) < 0)
log_warning("Cannot add isolate job for unit %s, ignoring: %s", u->meta.id, strerror(-r));
}
@ -1455,7 +1503,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
log_debug("Trying to enqueue job %s/%s", unit->meta.id, job_type_to_string(type));
if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, e, &ret)) < 0) {
if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false, e, &ret)) < 0) {
transaction_abort(m);
return r;
}

View file

@ -285,7 +285,7 @@ static int mount_add_default_dependencies(Mount *m) {
if ((r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_FSCK_TARGET, NULL, true)) < 0)
return r;
if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0)
if ((r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_UMOUNT_TARGET, NULL, true)) < 0)
return r;
}

View file

@ -110,7 +110,7 @@ static int path_add_default_dependencies(Path *p) {
if ((r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
return r;
return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_SHUTDOWN_TARGET, NULL, true);
}
static int path_load(Unit *u) {

View file

@ -873,7 +873,7 @@ static int service_add_default_dependencies(Service *s) {
}
/* Second, activate normal shutdown */
return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_SHUTDOWN_TARGET, NULL, true);
}
static int service_load(Unit *u) {

View file

@ -296,7 +296,7 @@ static int socket_add_default_dependencies(Socket *s) {
if ((r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
return r;
return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_SHUTDOWN_TARGET, NULL, true);
}
static int socket_load(Unit *u) {

View file

@ -311,6 +311,7 @@ static int dot_one_property(const char *name, const char *prop, DBusMessageIter
"RequisiteOverridable", "[color=\"darkblue\"]",
"Wants", "[color=\"darkgrey\"]",
"Conflicts", "[color=\"red\"]",
"ConflictedBy", "[color=\"red\"]",
"After", "[color=\"green\"]"
};

View file

@ -82,7 +82,7 @@ static int timer_add_default_dependencies(Timer *t) {
if ((r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true)) < 0)
return r;
return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTED_BY, SPECIAL_SHUTDOWN_TARGET, NULL, true);
}
static int timer_load(Unit *u) {

View file

@ -934,6 +934,10 @@ static void retroactively_start_dependencies(Unit *u) {
SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTS], i)
if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
manager_add_job(u->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
SET_FOREACH(other, u->meta.dependencies[UNIT_CONFLICTED_BY], i)
if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
manager_add_job(u->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL, NULL);
}
static void retroactively_stop_dependencies(Unit *u) {
@ -1312,7 +1316,8 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen
[UNIT_REQUIRED_BY] = _UNIT_DEPENDENCY_INVALID,
[UNIT_REQUIRED_BY_OVERRIDABLE] = _UNIT_DEPENDENCY_INVALID,
[UNIT_WANTED_BY] = _UNIT_DEPENDENCY_INVALID,
[UNIT_CONFLICTS] = UNIT_CONFLICTS,
[UNIT_CONFLICTS] = UNIT_CONFLICTED_BY,
[UNIT_CONFLICTED_BY] = UNIT_CONFLICTS,
[UNIT_BEFORE] = UNIT_AFTER,
[UNIT_AFTER] = UNIT_BEFORE,
[UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID,
@ -2138,6 +2143,7 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
[UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
[UNIT_WANTED_BY] = "WantedBy",
[UNIT_CONFLICTS] = "Conflicts",
[UNIT_CONFLICTED_BY] = "ConflictedBy",
[UNIT_BEFORE] = "Before",
[UNIT_AFTER] = "After",
[UNIT_REFERENCES] = "References",

View file

@ -108,7 +108,8 @@ enum UnitDependency {
UNIT_WANTED_BY, /* inverse of 'wants' */
/* Negative dependencies */
UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicts' */
UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */
UNIT_CONFLICTED_BY,
/* Order */
UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */

View file

@ -16,9 +16,6 @@ m4_dnl
m4_ifdef(`TARGET_FEDORA',
# On Fedora Runlevel 5 is graphical login
Names=runlevel5.target
# Pull in prefdm via requires, to make sure when it conflicts with
# getty@tty1.service it takes precedence.
Requires=prefdm.service
)m4_dnl
m4_ifdef(`TARGET_SUSE',
Names=runlevel5.target