Merge pull request #4678 from poettering/gc-device

Automatically GC device jobs when there's no need to keep them in the job queue anymore.
Implement systemctl list-jobs --before/--after.
Allow systemd-run -p After/Before/Wants/Requires= ...
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2016-11-16 21:16:13 -05:00
committed by GitHub
19 changed files with 575 additions and 72 deletions
-2
View File
@@ -560,7 +560,6 @@ Features:
- man: maybe sort directives in man pages, and take sections from --help and apply them to man too
* systemctl:
- systemctl list-jobs - show dependencies
- add systemctl switch to dump transaction without executing it
- Add a verbose mode to "systemctl start" and friends that explains what is being done or not done
- "systemctl disable" on a static unit prints no message and does
@@ -735,7 +734,6 @@ Features:
- maybe introduce WantsMountsFor=? Usecase:
http://lists.freedesktop.org/archives/systemd-devel/2015-January/027729.html
- recreate systemd's D-Bus private socket file on SIGUSR2
- GC unreferenced jobs (such as .device jobs)
- move PAM code into its own binary
- when we automatically restart a service, ensure we restart its rdeps, too.
- hide PAM options in fragment parser when compile time disabled
+12
View File
@@ -209,6 +209,10 @@
<varname>RequiresMountsFor=</varname>). Both explicitly
and implicitly introduced dependencies are shown with
<command>list-dependencies</command>.</para>
<para>When passed to the <command>list-jobs</command> command, for each printed job show which other jobs are
waiting for it. May be combined with <option>--before</option> to show both the jobs waiting for each job as
well as all jobs each job is waiting for.</para>
</listitem>
</varlistentry>
@@ -220,6 +224,10 @@
units that are ordered after the specified unit. In other
words, recursively list units following the
<varname>Before=</varname> dependency.</para>
<para>When passed to the <command>list-jobs</command> command, for each printed job show which other jobs it
is waiting for. May be combined with <option>--after</option> to show both the jobs waiting for each job as
well as all jobs each job is waiting for.</para>
</listitem>
</varlistentry>
@@ -1388,6 +1396,10 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service
<para>List jobs that are in progress. If one or more
<replaceable>PATTERN</replaceable>s are specified, only
jobs for units matching one of them are shown.</para>
<para>When combined with <option>--after</option> or <option>--before</option> the list is augmented with
information on which other job each job is waiting for, and which other jobs are waiting for it, see
above.</para>
</listitem>
</varlistentry>
<varlistentry>
+123 -3
View File
@@ -65,7 +65,7 @@ int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error
return r;
/* Access is granted to the job owner */
if (!sd_bus_track_contains(j->clients, sd_bus_message_get_sender(message))) {
if (!sd_bus_track_contains(j->bus_track, sd_bus_message_get_sender(message))) {
/* And for everybody else consult PolicyKit */
r = bus_verify_manage_units_async(j->unit->manager, message, error);
@@ -80,9 +80,61 @@ int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_reply_method_return(message, NULL);
}
int bus_job_method_get_waiting_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ Job **list = NULL;
Job *j = userdata;
int r, i, n;
if (strstr(sd_bus_message_get_member(message), "After"))
n = job_get_after(j, &list);
else
n = job_get_before(j, &list);
if (n < 0)
return n;
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(usssoo)");
if (r < 0)
return r;
for (i = 0; i < n; i ++) {
_cleanup_free_ char *unit_path = NULL, *job_path = NULL;
job_path = job_dbus_path(list[i]);
if (!job_path)
return -ENOMEM;
unit_path = unit_dbus_path(list[i]->unit);
if (!unit_path)
return -ENOMEM;
r = sd_bus_message_append(reply, "(usssoo)",
list[i]->id,
list[i]->unit->id,
job_type_to_string(list[i]->type),
job_state_to_string(list[i]->state),
job_path,
unit_path);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
const sd_bus_vtable bus_job_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetAfter", NULL, "a(usssoo)", bus_job_method_get_waiting_jobs, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetBefore", NULL, "a(usssoo)", bus_job_method_get_waiting_jobs, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -143,7 +195,7 @@ void bus_job_send_change_signal(Job *j) {
j->in_dbus_queue = false;
}
r = bus_foreach_bus(j->manager, j->clients, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j);
r = bus_foreach_bus(j->manager, j->bus_track, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j);
if (r < 0)
log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id);
@@ -187,7 +239,75 @@ void bus_job_send_removed_signal(Job *j) {
if (!j->sent_dbus_new_signal)
bus_job_send_change_signal(j);
r = bus_foreach_bus(j->manager, j->clients, send_removed_signal, j);
r = bus_foreach_bus(j->manager, j->bus_track, send_removed_signal, j);
if (r < 0)
log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id);
}
static int bus_job_track_handler(sd_bus_track *t, void *userdata) {
Job *j = userdata;
assert(t);
assert(j);
j->bus_track = sd_bus_track_unref(j->bus_track); /* make sure we aren't called again */
/* Last client dropped off the bus, maybe we should GC this now? */
job_add_to_gc_queue(j);
return 0;
}
static int bus_job_allocate_bus_track(Job *j) {
int r;
assert(j);
if (j->bus_track)
return 0;
r = sd_bus_track_new(j->unit->manager->api_bus, &j->bus_track, bus_job_track_handler, j);
if (r < 0)
return r;
return 0;
}
int bus_job_coldplug_bus_track(Job *j) {
int r = 0;
assert(j);
if (strv_isempty(j->deserialized_clients))
goto finish;
if (!j->manager->api_bus)
goto finish;
r = bus_job_allocate_bus_track(j);
if (r < 0)
goto finish;
r = bus_track_add_name_many(j->bus_track, j->deserialized_clients);
finish:
j->deserialized_clients = strv_free(j->deserialized_clients);
return r;
}
int bus_job_track_sender(Job *j, sd_bus_message *m) {
int r;
assert(j);
assert(m);
if (sd_bus_message_get_bus(m) != j->unit->manager->api_bus) {
j->ref_by_private_bus = true;
return 0;
}
r = bus_job_allocate_bus_track(j);
if (r < 0)
return r;
return sd_bus_track_add_sender(j->bus_track, m);
}
+4
View File
@@ -26,6 +26,10 @@
extern const sd_bus_vtable bus_job_vtable[];
int bus_job_method_cancel(sd_bus_message *message, void *job, sd_bus_error *error);
int bus_job_method_get_waiting_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error);
void bus_job_send_change_signal(Job *j);
void bus_job_send_removed_signal(Job *j);
int bus_job_coldplug_bus_track(Job *j);
int bus_job_track_sender(Job *j, sd_bus_message *m);
+22
View File
@@ -2285,6 +2285,26 @@ static int method_get_unit_file_links(sd_bus_message *message, void *userdata, s
return sd_bus_send(NULL, reply, NULL);
}
static int method_get_job_waiting(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
uint32_t id;
Job *j;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "u", &id);
if (r < 0)
return r;
j = manager_get_job(m, id);
if (!j)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id);
return bus_job_method_get_waiting_jobs(message, j, error);
}
const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -2390,6 +2410,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetUnitProcesses", "s", "a(sus)", method_get_unit_processes, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetJobAfter", "u", "a(usssoo)", method_get_job_waiting, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetJobBefore", "u", "a(usssoo)", method_get_job_waiting, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED),
+9 -16
View File
@@ -22,6 +22,7 @@
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "cgroup-util.h"
#include "dbus-job.h"
#include "dbus-unit.h"
#include "dbus.h"
#include "fd-util.h"
@@ -1223,17 +1224,9 @@ int bus_unit_queue_job(
if (r < 0)
return r;
if (sd_bus_message_get_bus(message) == u->manager->api_bus) {
if (!j->clients) {
r = sd_bus_track_new(sd_bus_message_get_bus(message), &j->clients, NULL, NULL);
if (r < 0)
return r;
}
r = sd_bus_track_add_sender(j->clients, message);
if (r < 0)
return r;
}
r = bus_job_track_sender(j, message);
if (r < 0)
return r;
path = job_dbus_path(j);
if (!path)
@@ -1507,7 +1500,7 @@ int bus_unit_check_load_state(Unit *u, sd_bus_error *error) {
return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id);
}
static int bus_track_handler(sd_bus_track *t, void *userdata) {
static int bus_unit_track_handler(sd_bus_track *t, void *userdata) {
Unit *u = userdata;
assert(t);
@@ -1519,7 +1512,7 @@ static int bus_track_handler(sd_bus_track *t, void *userdata) {
return 0;
}
static int allocate_bus_track(Unit *u) {
static int bus_unit_allocate_bus_track(Unit *u) {
int r;
assert(u);
@@ -1527,7 +1520,7 @@ static int allocate_bus_track(Unit *u) {
if (u->bus_track)
return 0;
r = sd_bus_track_new(u->manager->api_bus, &u->bus_track, bus_track_handler, u);
r = sd_bus_track_new(u->manager->api_bus, &u->bus_track, bus_unit_track_handler, u);
if (r < 0)
return r;
@@ -1545,7 +1538,7 @@ int bus_unit_track_add_name(Unit *u, const char *name) {
assert(u);
r = allocate_bus_track(u);
r = bus_unit_allocate_bus_track(u);
if (r < 0)
return r;
@@ -1557,7 +1550,7 @@ int bus_unit_track_add_sender(Unit *u, sd_bus_message *m) {
assert(u);
r = allocate_bus_track(u);
r = bus_unit_allocate_bus_track(u);
if (r < 0)
return r;
+3 -13
View File
@@ -1054,8 +1054,8 @@ static void destroy_bus(Manager *m, sd_bus **bus) {
m->subscribed = sd_bus_track_unref(m->subscribed);
HASHMAP_FOREACH(j, m->jobs, i)
if (j->clients && sd_bus_track_get_bus(j->clients) == *bus)
j->clients = sd_bus_track_unref(j->clients);
if (j->bus_track && sd_bus_track_get_bus(j->bus_track) == *bus)
j->bus_track = sd_bus_track_unref(j->bus_track);
/* Get rid of queued message on this bus */
if (m->queued_message && sd_bus_message_get_bus(m->queued_message) == *bus)
@@ -1185,7 +1185,6 @@ void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix) {
}
int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l) {
char **i;
int r = 0;
assert(m);
@@ -1207,16 +1206,7 @@ int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l) {
if (r < 0)
return r;
r = 0;
STRV_FOREACH(i, l) {
int k;
k = sd_bus_track_add_name(*t, *i);
if (k < 0)
r = k;
}
return r;
return bus_track_add_name_many(*t, l);
}
int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) {
+2
View File
@@ -831,6 +831,8 @@ const UnitVTable device_vtable = {
"Device\0"
"Install\0",
.gc_jobs = true,
.init = device_init,
.done = device_done,
.load = unit_load_fragment_and_dropin_optional,
+244 -13
View File
@@ -90,9 +90,12 @@ void job_free(Job *j) {
if (j->in_dbus_queue)
LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j);
if (j->in_gc_queue)
LIST_REMOVE(gc_queue, j->manager->gc_job_queue, j);
sd_event_source_unref(j->timer_event_source);
sd_bus_track_unref(j->clients);
sd_bus_track_unref(j->bus_track);
strv_free(j->deserialized_clients);
free(j);
@@ -226,6 +229,9 @@ Job* job_install(Job *j) {
log_unit_debug(j->unit,
"Installed new job %s/%s as %u",
j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
job_add_to_gc_queue(j);
return j;
}
@@ -267,7 +273,8 @@ JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool
* this means the 'anchor' job (i.e. the one the user
* explicitly asked for) is the requester. */
if (!(l = new0(JobDependency, 1)))
l = new0(JobDependency, 1);
if (!l)
return NULL;
l->subject = subject;
@@ -457,9 +464,7 @@ static bool job_is_runnable(Job *j) {
if (j->type == JOB_NOP)
return true;
if (j->type == JOB_START ||
j->type == JOB_VERIFY_ACTIVE ||
j->type == JOB_RELOAD) {
if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) {
/* Immediate result is that the job is or might be
* started. In this case let's wait for the
@@ -476,8 +481,7 @@ static bool job_is_runnable(Job *j) {
SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i)
if (other->job &&
(other->job->type == JOB_STOP ||
other->job->type == JOB_RESTART))
IN_SET(other->job->type, JOB_STOP, JOB_RESTART))
return false;
/* This means that for a service a and a service b where b
@@ -641,6 +645,7 @@ _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobR
[JOB_DEPENDENCY] = "Dependency failed for %s.",
[JOB_ASSERT] = "Assertion failed for %s.",
[JOB_UNSUPPORTED] = "Starting of %s not supported.",
[JOB_COLLECTED] = "Unecessary job for %s was removed.",
};
static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = {
[JOB_DONE] = "Stopped %s.",
@@ -700,6 +705,7 @@ static void job_print_status_message(Unit *u, JobType t, JobResult result) {
[JOB_SKIPPED] = { ANSI_HIGHLIGHT, " INFO " },
[JOB_ASSERT] = { ANSI_HIGHLIGHT_YELLOW, "ASSERT" },
[JOB_UNSUPPORTED] = { ANSI_HIGHLIGHT_YELLOW, "UNSUPP" },
[JOB_COLLECTED] = { ANSI_HIGHLIGHT, " INFO " },
};
const char *format;
@@ -751,6 +757,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) {
[JOB_INVALID] = LOG_INFO,
[JOB_ASSERT] = LOG_WARNING,
[JOB_UNSUPPORTED] = LOG_WARNING,
[JOB_COLLECTED] = LOG_INFO,
};
assert(u);
@@ -862,6 +869,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr
job_set_state(j, JOB_WAITING);
job_add_to_run_queue(j);
job_add_to_gc_queue(j);
goto finish;
}
@@ -905,11 +913,15 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr
finish:
/* Try to start the next jobs that can be started */
SET_FOREACH(other, u->dependencies[UNIT_AFTER], i)
if (other->job)
if (other->job) {
job_add_to_run_queue(other->job);
job_add_to_gc_queue(other->job);
}
SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i)
if (other->job)
if (other->job) {
job_add_to_run_queue(other->job);
job_add_to_gc_queue(other->job);
}
manager_check_finished(u->manager);
@@ -1012,7 +1024,7 @@ int job_serialize(Job *j, FILE *f) {
if (j->begin_usec > 0)
fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec);
bus_track_serialize(j->clients, f, "subscribed");
bus_track_serialize(j->bus_track, f, "subscribed");
/* End marker */
fputc('\n', f);
@@ -1123,12 +1135,14 @@ int job_coldplug(Job *j) {
/* After deserialization is complete and the bus connection
* set up again, let's start watching our subscribers again */
(void) bus_track_coldplug(j->manager, &j->clients, false, j->deserialized_clients);
j->deserialized_clients = strv_free(j->deserialized_clients);
(void) bus_job_coldplug_bus_track(j);
if (j->state == JOB_WAITING)
job_add_to_run_queue(j);
/* Maybe due to new dependencies we don't actually need this job anymore? */
job_add_to_gc_queue(j);
if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY)
return 0;
@@ -1203,9 +1217,225 @@ int job_get_timeout(Job *j, usec_t *timeout) {
return 1;
}
bool job_check_gc(Job *j) {
Unit *other;
Iterator i;
assert(j);
/* Checks whether this job should be GC'ed away. We only do this for jobs of units that have no effect on their
* own and just track external state. For now the only unit type that qualifies for this are .device units. */
if (!UNIT_VTABLE(j->unit)->gc_jobs)
return true;
if (sd_bus_track_count(j->bus_track) > 0)
return true;
/* FIXME: So this is a bit ugly: for now we don't properly track references made via private bus connections
* (because it's nasty, as sd_bus_track doesn't apply to it). We simply remember that the job was once
* referenced by one, and reset this whenever we notice that no private bus connections are around. This means
* the GC is a bit too conservative when it comes to jobs created by private bus connections. */
if (j->ref_by_private_bus) {
if (set_isempty(j->unit->manager->private_buses))
j->ref_by_private_bus = false;
else
return true;
}
if (j->type == JOB_NOP)
return true;
/* If a job is ordered after ours, and is to be started, then it needs to wait for us, regardless if we stop or
* start, hence let's not GC in that case. */
SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) {
if (!other->job)
continue;
if (other->job->ignore_order)
continue;
if (IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD))
return true;
}
/* If we are going down, but something else is orederd After= us, then it needs to wait for us */
if (IN_SET(j->type, JOB_STOP, JOB_RESTART)) {
SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) {
if (!other->job)
continue;
if (other->job->ignore_order)
continue;
return true;
}
}
/* The logic above is kinda the inverse of the job_is_runnable() logic. Specifically, if the job "we" is
* ordered before the job "other":
*
* we start + other start → stay
* we start + other stop → gc
* we stop + other start → stay
* we stop + other stop → gc
*
* "we" are ordered after "other":
*
* we start + other start → gc
* we start + other stop → gc
* we stop + other start → stay
* we stop + other stop → stay
*
*/
return false;
}
void job_add_to_gc_queue(Job *j) {
assert(j);
if (j->in_gc_queue)
return;
if (job_check_gc(j))
return;
LIST_PREPEND(gc_queue, j->unit->manager->gc_job_queue, j);
j->in_gc_queue = true;
}
static int job_compare(const void *a, const void *b) {
Job *x = *(Job**) a, *y = *(Job**) b;
if (x->id < y->id)
return -1;
if (x->id > y->id)
return 1;
return 0;
}
static size_t sort_job_list(Job **list, size_t n) {
Job *previous = NULL;
size_t a, b;
/* Order by numeric IDs */
qsort_safe(list, n, sizeof(Job*), job_compare);
/* Filter out duplicates */
for (a = 0, b = 0; a < n; a++) {
if (previous == list[a])
continue;
previous = list[b++] = list[a];
}
return b;
}
int job_get_before(Job *j, Job*** ret) {
_cleanup_free_ Job** list = NULL;
size_t n = 0, n_allocated = 0;
Unit *other = NULL;
Iterator i;
/* Returns a list of all pending jobs that need to finish before this job may be started. */
assert(j);
assert(ret);
if (j->ignore_order) {
*ret = NULL;
return 0;
}
if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) {
SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) {
if (!other->job)
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
return -ENOMEM;
list[n++] = other->job;
}
}
SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) {
if (!other->job)
continue;
if (!IN_SET(other->job->type, JOB_STOP, JOB_RESTART))
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
return -ENOMEM;
list[n++] = other->job;
}
n = sort_job_list(list, n);
*ret = list;
list = NULL;
return (int) n;
}
int job_get_after(Job *j, Job*** ret) {
_cleanup_free_ Job** list = NULL;
size_t n = 0, n_allocated = 0;
Unit *other = NULL;
Iterator i;
assert(j);
assert(ret);
/* Returns a list of all pending jobs that are waiting for this job to finish. */
SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) {
if (!other->job)
continue;
if (other->job->ignore_order)
continue;
if (!IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD))
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
return -ENOMEM;
list[n++] = other->job;
}
if (IN_SET(j->type, JOB_STOP, JOB_RESTART)) {
SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) {
if (!other->job)
continue;
if (other->job->ignore_order)
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
return -ENOMEM;
list[n++] = other->job;
}
}
n = sort_job_list(list, n);
*ret = list;
list = NULL;
return (int) n;
}
static const char* const job_state_table[_JOB_STATE_MAX] = {
[JOB_WAITING] = "waiting",
[JOB_RUNNING] = "running"
[JOB_RUNNING] = "running",
};
DEFINE_STRING_TABLE_LOOKUP(job_state, JobState);
@@ -1246,6 +1476,7 @@ static const char* const job_result_table[_JOB_RESULT_MAX] = {
[JOB_INVALID] = "invalid",
[JOB_ASSERT] = "assert",
[JOB_UNSUPPORTED] = "unsupported",
[JOB_COLLECTED] = "collected",
};
DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult);
+13 -3
View File
@@ -107,6 +107,7 @@ enum JobResult {
JOB_INVALID, /* JOB_RELOAD of inactive unit */
JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */
JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */
JOB_COLLECTED, /* Job was garbage collected, since nothing needed it anymore */
_JOB_RESULT_MAX,
_JOB_RESULT_INVALID = -1
};
@@ -122,8 +123,8 @@ struct JobDependency {
LIST_FIELDS(JobDependency, subject);
LIST_FIELDS(JobDependency, object);
bool matters;
bool conflicts;
bool matters:1;
bool conflicts:1;
};
struct Job {
@@ -133,6 +134,7 @@ struct Job {
LIST_FIELDS(Job, transaction);
LIST_FIELDS(Job, run_queue);
LIST_FIELDS(Job, dbus_queue);
LIST_FIELDS(Job, gc_queue);
LIST_HEAD(JobDependency, subject_list);
LIST_HEAD(JobDependency, object_list);
@@ -156,7 +158,7 @@ struct Job {
*
* There can be more than one client, because of job merging.
*/
sd_bus_track *clients;
sd_bus_track *bus_track;
char **deserialized_clients;
JobResult result;
@@ -168,6 +170,8 @@ struct Job {
bool sent_dbus_new_signal:1;
bool ignore_order:1;
bool irreversible:1;
bool in_gc_queue:1;
bool ref_by_private_bus:1;
};
Job* job_new(Unit *unit, JobType type);
@@ -227,6 +231,12 @@ void job_shutdown_magic(Job *j);
int job_get_timeout(Job *j, usec_t *timeout) _pure_;
bool job_check_gc(Job *j);
void job_add_to_gc_queue(Job *j);
int job_get_before(Job *j, Job*** ret);
int job_get_after(Job *j, Job*** ret);
const char* job_type_to_string(JobType t) _const_;
JobType job_type_from_string(const char *s) _pure_;
+33 -8
View File
@@ -981,10 +981,9 @@ good:
unit_gc_mark_good(u, gc_marker);
}
static unsigned manager_dispatch_gc_queue(Manager *m) {
static unsigned manager_dispatch_gc_unit_queue(Manager *m) {
unsigned n = 0, gc_marker;
Unit *u;
unsigned n = 0;
unsigned gc_marker;
assert(m);
@@ -996,12 +995,12 @@ static unsigned manager_dispatch_gc_queue(Manager *m) {
gc_marker = m->gc_marker;
while ((u = m->gc_queue)) {
while ((u = m->gc_unit_queue)) {
assert(u->in_gc_queue);
unit_gc_sweep(u, gc_marker);
LIST_REMOVE(gc_queue, m->gc_queue, u);
LIST_REMOVE(gc_queue, m->gc_unit_queue, u);
u->in_gc_queue = false;
n++;
@@ -1015,7 +1014,29 @@ static unsigned manager_dispatch_gc_queue(Manager *m) {
}
}
m->n_in_gc_queue = 0;
return n;
}
static unsigned manager_dispatch_gc_job_queue(Manager *m) {
unsigned n = 0;
Job *j;
assert(m);
while ((j = m->gc_job_queue)) {
assert(j->in_gc_queue);
LIST_REMOVE(gc_queue, m->gc_job_queue, j);
j->in_gc_queue = false;
n++;
if (job_check_gc(j))
continue;
log_unit_debug(j->unit, "Collecting job.");
(void) job_finish_and_invalidate(j, JOB_COLLECTED, false, false);
}
return n;
}
@@ -1035,7 +1056,8 @@ static void manager_clear_jobs_and_units(Manager *m) {
assert(!m->dbus_unit_queue);
assert(!m->dbus_job_queue);
assert(!m->cleanup_queue);
assert(!m->gc_queue);
assert(!m->gc_unit_queue);
assert(!m->gc_job_queue);
assert(hashmap_isempty(m->jobs));
assert(hashmap_isempty(m->units));
@@ -2228,7 +2250,10 @@ int manager_loop(Manager *m) {
if (manager_dispatch_load_queue(m) > 0)
continue;
if (manager_dispatch_gc_queue(m) > 0)
if (manager_dispatch_gc_job_queue(m) > 0)
continue;
if (manager_dispatch_gc_unit_queue(m) > 0)
continue;
if (manager_dispatch_cleanup_queue(m) > 0)
+3 -3
View File
@@ -104,8 +104,9 @@ struct Manager {
/* Units to remove */
LIST_HEAD(Unit, cleanup_queue);
/* Units to check when doing GC */
LIST_HEAD(Unit, gc_queue);
/* Units and jobs to check when doing GC */
LIST_HEAD(Unit, gc_unit_queue);
LIST_HEAD(Job, gc_job_queue);
/* Units that should be realized */
LIST_HEAD(Unit, cgroup_queue);
@@ -229,7 +230,6 @@ struct Manager {
int pin_cgroupfs_fd;
int gc_marker;
unsigned n_in_gc_queue;
/* Flags */
ManagerExitCode exit_code:5;
+16
View File
@@ -64,6 +64,14 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetJob"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetJobAfter"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetJobBefore"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="ListUnits"/>
@@ -250,6 +258,14 @@
send_interface="org.freedesktop.systemd1.Job"
send_member="Cancel"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Job"
send_member="GetAfter"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Job"
send_member="GetBefore"/>
<allow receive_sender="org.freedesktop.systemd1"/>
</policy>
+3 -7
View File
@@ -389,10 +389,8 @@ void unit_add_to_gc_queue(Unit *u) {
if (unit_check_gc(u))
return;
LIST_PREPEND(gc_queue, u->manager->gc_queue, u);
LIST_PREPEND(gc_queue, u->manager->gc_unit_queue, u);
u->in_gc_queue = true;
u->manager->n_in_gc_queue++;
}
void unit_add_to_dbus_queue(Unit *u) {
@@ -570,10 +568,8 @@ void unit_free(Unit *u) {
if (u->in_cleanup_queue)
LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u);
if (u->in_gc_queue) {
LIST_REMOVE(gc_queue, u->manager->gc_queue, u);
u->manager->n_in_gc_queue--;
}
if (u->in_gc_queue)
LIST_REMOVE(gc_queue, u->manager->gc_unit_queue, u);
if (u->in_cgroup_queue)
LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u);
+3
View File
@@ -441,6 +441,9 @@ struct UnitVTable {
/* True if transient units of this type are OK */
bool can_transient:1;
/* True if queued jobs of this type should be GC'ed if no other job needs them anymore */
bool gc_jobs:1;
};
extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX];
+7 -2
View File
@@ -62,6 +62,7 @@ int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) {
const char *eq, *field;
UnitDependency dep;
int r, rl;
assert(m);
@@ -572,7 +573,9 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
flags = (~flags) & NAMESPACE_FLAGS_ALL;
r = sd_bus_message_append(m, "v", "t", flags);
} else {
} else if ((dep = unit_dependency_from_string(field)) >= 0)
r = sd_bus_message_append(m, "v", "as", 1, eq);
else {
log_error("Unknown assignment %s.", assignment);
return -EINVAL;
}
@@ -838,6 +841,8 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const*
log_error("Assertion failed on job for %s.", strna(d->name));
else if (streq(d->result, "unsupported"))
log_error("Operation on or unit type of %s not supported on this system.", strna(d->name));
else if (streq(d->result, "collected"))
log_error("Queued job for %s was garbage collected.", strna(d->name));
else if (!streq(d->result, "done") && !streq(d->result, "skipped")) {
if (d->name) {
int q;
@@ -853,7 +858,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const*
}
}
if (streq(d->result, "canceled"))
if (STR_IN_SET(d->result, "canceled", "collected"))
r = -ECANCELED;
else if (streq(d->result, "timeout"))
r = -ETIME;
+19
View File
@@ -1583,3 +1583,22 @@ int bus_property_get_rlimit(
return sd_bus_message_append(reply, "t", u);
}
int bus_track_add_name_many(sd_bus_track *t, char **l) {
int r = 0;
char **i;
assert(t);
/* Continues adding after failure, and returns the first failure. */
STRV_FOREACH(i, l) {
int k;
k = sd_bus_track_add_name(t, *i);
if (k < 0 && r >= 0)
r = k;
}
return r;
}
+2
View File
@@ -159,3 +159,5 @@ int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id,
int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external);
int bus_property_get_rlimit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
int bus_track_add_name_many(sd_bus_track *t, char **l);
+57 -2
View File
@@ -173,6 +173,8 @@ static OutputMode arg_output = OUTPUT_SHORT;
static bool arg_plain = false;
static bool arg_firmware_setup = false;
static bool arg_now = false;
static bool arg_jobs_before = false;
static bool arg_jobs_after = false;
static int daemon_reload(int argc, char *argv[], void* userdata);
static int trivial_method(int argc, char *argv[], void *userdata);
@@ -204,6 +206,9 @@ static int acquire_bus(BusFocus focus, sd_bus **ret) {
if (arg_transport != BUS_TRANSPORT_LOCAL)
focus = BUS_FULL;
if (getenv_bool("SYSTEMCTL_FORCE_BUS") > 0)
focus = BUS_FULL;
if (!busses[focus]) {
bool user;
@@ -2192,12 +2197,55 @@ finish:
return r;
}
static void output_waiting_jobs(sd_bus *bus, uint32_t id, const char *method, const char *prefix) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *name, *type, *state, *job_path, *unit_path;
uint32_t other_id;
int r;
assert(bus);
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
method,
&error,
&reply,
"u", id);
if (r < 0) {
log_debug_errno(r, "Failed to get waiting jobs for job %" PRIu32, id);
return;
}
r = sd_bus_message_enter_container(reply, 'a', "(usssoo)");
if (r < 0) {
bus_log_parse_error(r);
return;
}
while ((r = sd_bus_message_read(reply, "(usssoo)", &other_id, &name, &type, &state, &job_path, &unit_path)) > 0)
printf("%s%u (%s/%s)\n", prefix, other_id, name, type);
if (r < 0) {
bus_log_parse_error(r);
return;
}
r = sd_bus_message_exit_container(reply);
if (r < 0) {
bus_log_parse_error(r);
return;
}
}
struct job_info {
uint32_t id;
const char *name, *type, *state;
};
static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipped) {
static void output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n, bool skipped) {
unsigned id_len, unit_len, type_len, state_len;
const struct job_info *j;
const char *on, *off;
@@ -2259,6 +2307,11 @@ static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipp
on, unit_len, e ? e : j->name, off,
type_len, j->type,
on, state_len, j->state, off);
if (arg_jobs_after)
output_waiting_jobs(bus, j->id, "GetJobAfter", "\tA job waits for this job: ");
if (arg_jobs_before)
output_waiting_jobs(bus, j->id, "GetJobBefore", "\tThis job waits for a job: ");
}
if (!arg_no_legend) {
@@ -2327,7 +2380,7 @@ static int list_jobs(int argc, char *argv[], void *userdata) {
pager_open(arg_no_pager, false);
output_jobs_list(jobs, c, skipped);
output_jobs_list(bus, jobs, c, skipped);
return 0;
}
@@ -7302,10 +7355,12 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
case ARG_AFTER:
arg_dependency = DEPENDENCY_AFTER;
arg_jobs_after = true;
break;
case ARG_BEFORE:
arg_dependency = DEPENDENCY_BEFORE;
arg_jobs_before = true;
break;
case ARG_SHOW_TYPES: