Commit 701cc384 authored by Lennart Poettering's avatar Lennart Poettering
Browse files

manager: automatically GC unreferenced units

parent 48507e66
......@@ -149,7 +149,7 @@ static int automount_load(Unit *u) {
if ((r = unit_load_related_unit(u, ".mount", (Unit**) &a->mount)) < 0)
return r;
if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount))) < 0)
if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount), true)) < 0)
return r;
}
......@@ -640,13 +640,20 @@ static const char *automount_sub_state_to_string(Unit *u) {
return automount_state_to_string(AUTOMOUNT(u)->state);
}
static bool automount_check_gc(Unit *u) {
Automount *a = AUTOMOUNT(u);
assert(a);
return UNIT_VTABLE(UNIT(a->mount))->check_gc(UNIT(a->mount));
}
static void automount_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
Automount *a = AUTOMOUNT(u);
union autofs_v5_packet_union packet;
ssize_t l;
int r;
Automount *a = AUTOMOUNT(u);
assert(a);
assert(fd == a->pipe_fd);
......@@ -729,6 +736,8 @@ const UnitVTable automount_vtable = {
.active_state = automount_active_state,
.sub_state_to_string = automount_sub_state_to_string,
.check_gc = automount_check_gc,
.fd_event = automount_fd_event,
.bus_message_handler = bus_automount_message_handler,
......
......@@ -166,7 +166,7 @@ void bus_job_send_change_signal(Job *j) {
} else {
/* Send a new signal */
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1", "JobNew")))
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew")))
goto oom;
if (!dbus_message_append_args(m,
......@@ -207,7 +207,7 @@ void bus_job_send_removed_signal(Job *j) {
if (!(p = job_dbus_path(j)))
goto oom;
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1", "JobRemoved")))
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved")))
goto oom;
if (!dbus_message_append_args(m,
......
......@@ -199,7 +199,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection
DBUS_TYPE_INVALID))
goto oom;
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "GetJob")) {
} else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetJob")) {
uint32_t id;
Job *j;
......
......@@ -362,7 +362,7 @@ void bus_unit_send_change_signal(Unit *u) {
} else {
/* Send a new signal */
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1", "UnitNew")))
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitNew")))
goto oom;
if (!dbus_message_append_args(m,
......@@ -403,7 +403,7 @@ void bus_unit_send_removed_signal(Unit *u) {
if (!(p = unit_dbus_path(u)))
goto oom;
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1", "UnitRemoved")))
if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitRemoved")))
goto oom;
if (!dbus_message_append_args(m,
......
......@@ -244,7 +244,7 @@ static int device_process_new_device(Manager *m, struct udev_device *dev, bool u
goto fail;
}
r = unit_add_dependency_by_name(u, UNIT_WANTS, NULL, e);
r = unit_add_dependency_by_name(u, UNIT_WANTS, NULL, e, true);
free(e);
if (r < 0)
......
......@@ -115,7 +115,7 @@ static void change_runlevel(Server *s, int runlevel) {
log_debug("Running request %s", target);
if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1", "GetUnit"))) {
if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "GetUnit"))) {
log_error("Could not allocate message.");
goto finish;
}
......
......@@ -58,8 +58,10 @@ void job_free(Job *j) {
if (j->installed) {
bus_job_send_removed_signal(j);
if (j->unit->meta.job == j)
if (j->unit->meta.job == j) {
j->unit->meta.job = NULL;
unit_add_to_gc_queue(j->unit);
}
hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
j->installed = false;
......
......@@ -52,7 +52,7 @@ static int iterate_dir(Unit *u, const char *path) {
goto finish;
}
r = unit_add_dependency_by_name(u, UNIT_WANTS, de->d_name, f);
r = unit_add_dependency_by_name(u, UNIT_WANTS, de->d_name, f, true);
free(f);
if (r < 0)
......
......@@ -97,7 +97,7 @@ static int config_parse_deps(
if (!k)
return -ENOMEM;
r = unit_add_dependency_by_name(u, d, k, NULL);
r = unit_add_dependency_by_name(u, d, k, NULL, true);
free(k);
if (r < 0)
......
......@@ -52,6 +52,12 @@
#include "dbus-unit.h"
#include "dbus-job.h"
/* As soon as 16 units are in our GC queue, make sure to run a gc sweep */
#define GC_QUEUE_ENTRIES_MAX 16
/* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
#define GC_QUEUE_USEC_MAX (5*USEC_PER_SEC)
static int enable_special_signals(Manager *m) {
char fd;
......@@ -389,6 +395,77 @@ static unsigned manager_dispatch_cleanup_queue(Manager *m) {
return n;
}
static void unit_gc_sweep(Unit *u, int gc_marker) {
Iterator i;
Unit *other;
assert(u);
if (u->meta.gc_marker == gc_marker ||
u->meta.gc_marker == -gc_marker)
return;
if (!u->meta.in_cleanup_queue)
goto bad;
if (unit_check_gc(u))
goto good;
SET_FOREACH(other, u->meta.dependencies[UNIT_REFERENCED_BY], i) {
unit_gc_sweep(other, gc_marker);
if (other->meta.gc_marker == gc_marker)
goto good;
}
bad:
/* So there is no reason to keep this unit around, hence let's get rid of it */
u->meta.gc_marker = -gc_marker;
return;
good:
u->meta.gc_marker = gc_marker;
}
static unsigned manager_dispatch_gc_queue(Manager *m) {
Meta *meta;
unsigned n = 0;
int gc_marker;
assert(m);
if ((m->n_in_gc_queue < GC_QUEUE_ENTRIES_MAX) &&
(m->gc_queue_timestamp <= 0 ||
(m->gc_queue_timestamp + GC_QUEUE_USEC_MAX) > now(CLOCK_MONOTONIC)))
return 0;
log_debug("Running GC...");
gc_marker = m->gc_marker;
m->gc_marker = MIN(0, m->gc_marker + 1);
while ((meta = m->gc_queue)) {
assert(meta->in_gc_queue);
LIST_REMOVE(Meta, gc_queue, m->gc_queue, meta);
meta->in_gc_queue = false;
n++;
unit_gc_sweep(UNIT(meta), gc_marker);
if (meta->gc_marker == -gc_marker) {
log_debug("Collecting %s", meta->id);
unit_add_to_cleanup_queue(UNIT(meta));
}
}
m->n_in_gc_queue = 0;
m->gc_queue_timestamp = 0;
return n;
}
static void manager_clear_jobs_and_units(Manager *m) {
Job *j;
Unit *u;
......@@ -1770,6 +1847,9 @@ int manager_loop(Manager *m) {
if (manager_dispatch_cleanup_queue(m) > 0)
continue;
if (manager_dispatch_gc_queue(m) > 0)
continue;
if (manager_dispatch_load_queue(m) > 0)
continue;
......
......@@ -145,8 +145,12 @@ struct Manager {
LIST_HEAD(Meta, dbus_unit_queue);
LIST_HEAD(Job, dbus_job_queue);
/* Units to remove */
LIST_HEAD(Meta, cleanup_queue);
/* Units to check when doing GC */
LIST_HEAD(Meta, gc_queue);
/* Jobs to be added */
Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */
JobDependency *transaction_anchor;
......@@ -193,6 +197,11 @@ struct Manager {
char *cgroup_controller;
char *cgroup_hierarchy;
usec_t gc_queue_timestamp;
int gc_marker;
unsigned n_in_gc_queue;
/* Flags */
ManagerRunningAs running_as;
ManagerExitCode exit_code:4;
......
......@@ -144,15 +144,15 @@ static int mount_add_node_links(Mount *m) {
if (r < 0)
return r;
if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, device)) < 0)
if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, device, true)) < 0)
return r;
if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, device)) < 0)
if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, device, true)) < 0)
return r;
if (UNIT(m)->meta.manager->running_as == MANAGER_INIT ||
UNIT(m)->meta.manager->running_as == MANAGER_SYSTEM)
if ((r = unit_add_dependency(device, UNIT_WANTS, UNIT(m))) < 0)
if ((r = unit_add_dependency(device, UNIT_WANTS, UNIT(m), false)) < 0)
return r;
return 0;
......@@ -180,20 +180,20 @@ static int mount_add_path_links(Mount *m) {
if (path_startswith(m->where, n->where)) {
if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(other))) < 0)
if ((r = unit_add_dependency(UNIT(m), UNIT_AFTER, UNIT(other), true)) < 0)
return r;
if (n->from_etc_fstab || n->from_fragment)
if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(other))) < 0)
if ((r = unit_add_dependency(UNIT(m), UNIT_REQUIRES, UNIT(other), true)) < 0)
return r;
} else if (path_startswith(n->where, m->where)) {
if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(other))) < 0)
if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(other), true)) < 0)
return r;
if (m->from_etc_fstab || m->from_fragment)
if ((r = unit_add_dependency(UNIT(other), UNIT_REQUIRES, UNIT(m))) < 0)
if ((r = unit_add_dependency(UNIT(other), UNIT_REQUIRES, UNIT(m), true)) < 0)
return r;
}
}
......@@ -256,18 +256,18 @@ static int mount_add_target_links(Mount *m) {
if ((r = unit_load_related_unit(UNIT(m), ".automount", &am)) < 0)
return r;
if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(am))) < 0)
if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(am), true)) < 0)
return r;
return unit_add_dependency(UNIT(am), UNIT_BEFORE, tu);
return unit_add_dependency(UNIT(am), UNIT_BEFORE, tu, true);
} else {
if (handle)
if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m))) < 0)
if ((r = unit_add_dependency(tu, UNIT_WANTS, UNIT(m), true)) < 0)
return r;
return unit_add_dependency(UNIT(m), UNIT_BEFORE, tu);
return unit_add_dependency(UNIT(m), UNIT_BEFORE, tu, true);
}
}
......@@ -870,6 +870,14 @@ static const char *mount_sub_state_to_string(Unit *u) {
return mount_state_to_string(MOUNT(u)->state);
}
static bool mount_check_gc(Unit *u) {
Mount *m = MOUNT(u);
assert(m);
return m->from_etc_fstab || m->from_proc_self_mountinfo;
}
static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
Mount *m = MOUNT(u);
bool success;
......@@ -1473,6 +1481,8 @@ const UnitVTable mount_vtable = {
.active_state = mount_active_state,
.sub_state_to_string = mount_sub_state_to_string,
.check_gc = mount_check_gc,
.sigchld_event = mount_sigchld_event,
.timer_event = mount_timer_event,
......
......@@ -45,31 +45,31 @@
send_member="GetAll"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="GetJob"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="ListUnits"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="ListJobs"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="Subscribe"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="Unsubscribe"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="Dump"/>
<allow receive_sender="org.freedesktop.systemd1"/>
......
......@@ -225,7 +225,7 @@ static int sysv_chkconfig_order(Service *s) {
/* FIXME: Maybe we should compare the name here lexicographically? */
if (!(r = unit_add_dependency(UNIT(s), d, UNIT(t))) < 0)
if (!(r = unit_add_dependency(UNIT(s), d, UNIT(t), true)) < 0)
return r;
}
......@@ -524,8 +524,8 @@ static int service_load_sysv_path(Service *s, const char *path) {
if (unit_name_to_type(m) == UNIT_SERVICE)
r = unit_add_name(u, m);
else {
if ((r = unit_add_dependency_by_name_inverse(u, UNIT_REQUIRES, m, NULL)) >= 0)
r = unit_add_dependency_by_name(u, UNIT_BEFORE, m, NULL);
if ((r = unit_add_dependency_by_name_inverse(u, UNIT_REQUIRES, m, NULL, true)) >= 0)
r = unit_add_dependency_by_name(u, UNIT_BEFORE, m, NULL, true);
}
free(m);
......@@ -558,7 +558,7 @@ static int service_load_sysv_path(Service *s, const char *path) {
if (r == 0)
continue;
r = unit_add_dependency_by_name(u, UNIT_AFTER, m, NULL);
r = unit_add_dependency_by_name(u, UNIT_AFTER, m, NULL, true);
free(m);
if (r < 0)
......@@ -654,8 +654,8 @@ static int service_load_sysv_path(Service *s, const char *path) {
* needed for early boot) and don't create any links
* to it. */
if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL)) < 0 ||
(r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL)) < 0)
if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0 ||
(r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
goto finish;
}
......@@ -1890,6 +1890,22 @@ static const char *service_sub_state_to_string(Unit *u) {
return service_state_to_string(SERVICE(u)->state);
}
static bool service_check_gc(Unit *u) {
Service *s = SERVICE(u);
assert(s);
return !!s->sysv_path;
}
static bool service_check_snapshot(Unit *u) {
Service *s = SERVICE(u);
assert(s);
return !s->got_socket_fd;
}
static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
Service *s = SERVICE(u);
bool success;
......@@ -2221,10 +2237,10 @@ static int service_enumerate(Manager *m) {
goto finish;
if (de->d_name[0] == 'S') {
if ((r = unit_add_dependency(runlevel, UNIT_WANTS, service)) < 0)
if ((r = unit_add_dependency(runlevel, UNIT_WANTS, service, true)) < 0)
goto finish;
if ((r = unit_add_dependency(runlevel, UNIT_AFTER, service)) < 0)
if ((r = unit_add_dependency(runlevel, UNIT_AFTER, service, true)) < 0)
goto finish;
} else if (de->d_name[0] == 'K' &&
......@@ -2238,10 +2254,10 @@ static int service_enumerate(Manager *m) {
* implicitly added by the
* core logic. */
if ((r = unit_add_dependency(runlevel, UNIT_CONFLICTS, service)) < 0)
if ((r = unit_add_dependency(runlevel, UNIT_CONFLICTS, service, true)) < 0)
goto finish;
if ((r = unit_add_dependency(runlevel, UNIT_BEFORE, service)) < 0)
if ((r = unit_add_dependency(runlevel, UNIT_BEFORE, service, true)) < 0)
goto finish;
}
}
......@@ -2342,6 +2358,7 @@ int service_set_socket_fd(Service *s, int fd) {
return -EAGAIN;
s->socket_fd = fd;
s->got_socket_fd = true;
return 0;
}
......@@ -2416,6 +2433,9 @@ const UnitVTable service_vtable = {
.active_state = service_active_state,
.sub_state_to_string = service_sub_state_to_string,
.check_gc = service_check_gc,
.check_snapshot = service_check_snapshot,
.sigchld_event = service_sigchld_event,
.timer_event = service_timer_event,
......
......@@ -110,6 +110,8 @@ struct Service {
bool bus_name_good:1;
bool got_socket_fd:1;
bool sysv_has_lsb:1;
char *sysv_path;
int sysv_start_priority;
......
......@@ -139,10 +139,10 @@ static int snapshot_deserialize_item(Unit *u, const char *key, const char *value
} else if (streq(key, "requires")) {
if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, value, NULL)) < 0)
if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, value, NULL, true)) < 0)
return r;
if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, value, NULL)) < 0)
if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, value, NULL, true)) < 0)
return r;
} else
log_debug("Unknown serialization key '%s'", key);
......@@ -211,13 +211,17 @@ int snapshot_create(Manager *m, const char *name, bool cleanup, Snapshot **_s) {
if (k != other->meta.id)
continue;
if (UNIT_VTABLE(other)->check_snapshot)
if (!UNIT_VTABLE(other)->check_snapshot(other))
continue;
if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other)))
continue;
if ((r = unit_add_dependency(u, UNIT_REQUIRES, other)) < 0)
if ((r = unit_add_dependency(u, UNIT_REQUIRES, other, true)) < 0)
goto fail;
if ((r = unit_add_dependency(u, UNIT_AFTER, other)) < 0)
if ((r = unit_add_dependency(u, UNIT_AFTER, other, true)) < 0)
goto fail;
}
......@@ -252,6 +256,7 @@ const UnitVTable snapshot_vtable = {
.no_alias = true,
.no_instances = true,
.no_snapshots = true,
.no_gc = true,
.load = unit_load_nop,
.coldplug = snapshot_coldplug,
......
......@@ -157,7 +157,7 @@ static int socket_load(Unit *u) {
if ((r = unit_load_related_unit(u, ".service", (Unit**) &s->service)))
return r;
if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0)
if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service), true)) < 0)
return r;
}
......
......@@ -216,7 +216,7 @@ public class MainWindow : Window {
manager = bus.get_object(
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1") as Manager;
"org.freedesktop.systemd1.Manager") as Manager;
manager.unit_new += on_unit_new;
manager.job_new += on_job_new;
......
......@@ -101,7 +101,7 @@ int main (string[] args) {
Manager manager = bus.get_object (
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1") as Manager;
"org.freedesktop.systemd1.Manager") as Manager;
if (args[1] == "list-units" || args.length <= 1) {
var list = manager.list_units();
......
......@@ -204,6 +204,25 @@ int unit_set_description(Unit *u, const char *description) {
return 0;
}
bool unit_check_gc(Unit *u) {
assert(u);
if (UNIT_VTABLE(u)->no_gc)
return true;
if (u->meta.job)
return true;
if (unit_active_state(u) != UNIT_INACTIVE)
return true;
if (UNIT_VTABLE(u)->check_gc)
if (UNIT_VTABLE(u)->check_gc(u))
return true;
return false;
}
void unit_add_to_load_queue(Unit *u) {
assert(u);
assert(u->meta.type != _UNIT_TYPE_INVALID);
......@@ -225,6 +244,24 @@ void unit_add_to_cleanup_queue(Unit *u) {
u->meta.in_cleanup_queue = true;
}
void unit_add_to_gc_queue(Unit *u) {
assert(u);
if (u->meta.in_gc_queue || u->meta.in_cleanup_queue)
return;
if (unit_check_gc(u))
return;
LIST_PREPEND(Meta, gc_queue, u->meta.manager->gc_queue, &u->meta);
u->meta.in_gc_queue = true;
u->meta.manager->n_in_gc_queue ++;