core: add bus calls for determining jobs waiting for other jobs
This should make it easier to debug job deadlocks.
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
+127
@@ -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",
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user