From 15ea79f85c3a9b174b6ba14cc9a0f5f482ca1553 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 16 Nov 2016 16:07:32 +0100 Subject: core: add bus calls for determining jobs waiting for other jobs This should make it easier to debug job deadlocks. --- src/core/dbus-job.c | 52 ++++++++++++++ src/core/dbus-job.h | 1 + src/core/dbus-manager.c | 22 ++++++ src/core/job.c | 127 +++++++++++++++++++++++++++++++++ src/core/job.h | 3 + src/core/org.freedesktop.systemd1.conf | 16 +++++ 6 files changed, 221 insertions(+) (limited to 'src') diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c index 7888c163f1..087a08dc0d 100644 --- a/src/core/dbus-job.c +++ b/src/core/dbus-job.c @@ -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), diff --git a/src/core/dbus-job.h b/src/core/dbus-job.h index f9148895be..a4366a0720 100644 --- a/src/core/dbus-job.h +++ b/src/core/dbus-job.h @@ -26,6 +26,7 @@ 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); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 5a7922a249..9af49dd1bc 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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), diff --git a/src/core/job.c b/src/core/job.c index d6e71d68ef..2ba4c78096 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -1306,6 +1306,133 @@ void job_add_to_gc_queue(Job *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", diff --git a/src/core/job.h b/src/core/job.h index 6fdec9f226..bea743f462 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -234,6 +234,9 @@ 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_; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index a61677e645..e824a2233c 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -64,6 +64,14 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="GetJob"/> + + + + @@ -250,6 +258,14 @@ send_interface="org.freedesktop.systemd1.Job" send_member="Cancel"/> + + + + -- cgit v1.2.3-54-g00ecf