[systemd-devel] [RFC] core: introduce ExitOnIdle= and ExitOnIdleSec=

WaLyong Cho walyong.cho at samsung.com
Mon Apr 20 07:56:05 PDT 2015


If a service does not consume CPU during some time(can be configured
by ExitOnIdleSec=) and set to stopped on idle state(ExitOnIdle=), the
service will be stopped. This can be useful if the service provides
some of activation methods.
---
 src/core/load-fragment-gperf.gperf.m4 |  2 +
 src/core/service.c                    | 97 +++++++++++++++++++++++++++++++++++
 src/core/service.h                    |  5 ++
 src/core/unit.h                       |  1 +
 4 files changed, 105 insertions(+)

diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 5305984..60d573e 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -229,6 +229,8 @@ Service.BusName,                 config_parse_bus_name,              0,
 Service.FileDescriptorStoreMax,  config_parse_unsigned,              0,                             offsetof(Service, n_fd_store_max)
 Service.NotifyAccess,            config_parse_notify_access,         0,                             offsetof(Service, notify_access)
 Service.Sockets,                 config_parse_service_sockets,       0,                             0
+Service.ExitOnIdle,              config_parse_bool,                  0,                             offsetof(Service, exit_on_idle)
+Service.ExitOnIdleSec,           config_parse_sec,                   0,                             offsetof(Service, exit_on_idle_usec)
 m4_ifdef(`ENABLE_KDBUS',
 `Service.BusPolicy,              config_parse_bus_endpoint_policy,   0,                             offsetof(Service, exec_context)',
 `Service.BusPolicy,              config_parse_warn_compat,           DISABLED_EXPERIMENTAL,         0')
diff --git a/src/core/service.c b/src/core/service.c
index fa818fc..c8752ae 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -91,6 +91,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
 static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
 static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
 static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata);
+static int service_dispatch_exit_on_idle_timer(sd_event_source *source, usec_t usec, void *userdata);
 
 static void service_enter_signal(Service *s, ServiceState state, ServiceResult f);
 static void service_enter_reload_by_notify(Service *s);
@@ -108,6 +109,8 @@ static void service_init(Unit *u) {
         s->socket_fd = -1;
         s->bus_endpoint_fd = -1;
         s->guess_main_pid = true;
+        s->exit_on_idle = false;
+        s->exit_on_idle_usec = 0;
 
         RATELIMIT_INIT(s->start_limit, u->manager->default_start_limit_interval, u->manager->default_start_limit_burst);
 
@@ -279,6 +282,56 @@ static void service_release_resources(Unit *u) {
         assert(s->n_fd_store == 0);
 }
 
+static void service_stop_exit_on_idle_timer(Service *s) {
+        assert(s);
+
+        s->exit_on_idle_event_source = sd_event_source_unref(s->exit_on_idle_event_source);
+        s->exit_on_idle_timestamp = DUAL_TIMESTAMP_NULL;
+}
+
+static void service_start_exit_on_idle_timer(Service *s) {
+        int r;
+
+        assert(s);
+
+        if (s->exit_on_idle_usec <= 0)
+                return;
+
+        if (s->exit_on_idle_event_source) {
+                r = sd_event_source_set_time(s->exit_on_idle_event_source, s->exit_on_idle_timestamp.monotonic + s->exit_on_idle_usec);
+                if (r < 0) {
+                        log_unit_warning(UNIT(s)->id, "%s failed to reset exit-on-idle timer: %s", UNIT(s)->id, strerror(-r));
+                        return;
+                }
+
+                r = sd_event_source_set_enabled(s->exit_on_idle_event_source, SD_EVENT_ON);
+        } else {
+                r = sd_event_add_time(
+                                UNIT(s)->manager->event,
+                                &s->exit_on_idle_event_source,
+                                CLOCK_MONOTONIC,
+                                s->exit_on_idle_timestamp.monotonic + s->exit_on_idle_usec, 0,
+                                service_dispatch_exit_on_idle_timer, s);
+                if (r < 0) {
+                        log_unit_warning(UNIT(s)->id, "%s failed to add exit-on-idle timer: %s", UNIT(s)->id, strerror(-r));
+                        return;
+                }
+
+                r = sd_event_source_set_priority(s->exit_on_idle_event_source, SD_EVENT_PRIORITY_IDLE);
+        }
+
+        if (r < 0)
+                log_unit_warning(UNIT(s)->id, "%s failed to install exit-on-idle timer: %s", UNIT(s)->id, strerror(-r));
+        return;
+}
+
+static void service_reset_exit_on_idle_timer(Service *s) {
+        assert(s);
+
+        dual_timestamp_get(&s->exit_on_idle_timestamp);
+        service_start_exit_on_idle_timer(s);
+}
+
 static void service_done(Unit *u) {
         Service *s = SERVICE(u);
 
@@ -518,6 +571,7 @@ static void service_fix_output(Service *s) {
 }
 
 static int service_add_extras(Service *s) {
+        CGroupContext *cc;
         int r;
 
         assert(s);
@@ -571,6 +625,15 @@ static int service_add_extras(Service *s) {
                         return r;
         }
 
+        if (s->exit_on_idle && s->exit_on_idle_usec <= 0)
+                s->exit_on_idle_usec = USEC_PER_MINUTE;
+
+        if (s->exit_on_idle) {
+                cc = unit_get_cgroup_context(UNIT(s));
+                if (cc)
+                        cc->cpu_accounting = true;
+        }
+
         if (UNIT(s)->default_dependencies) {
                 r = service_add_default_dependencies(s);
                 if (r < 0)
@@ -850,6 +913,12 @@ static void service_set_state(Service *s, ServiceState state) {
         if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
                 service_stop_watchdog(s);
 
+        if (IN_SET(state, SERVICE_RUNNING))
+                service_start_exit_on_idle_timer(s);
+
+        if (IN_SET(state, SERVICE_DEAD))
+                service_stop_exit_on_idle_timer(s);
+
         /* For the inactive states unit_notify() will trim the cgroup,
          * but for exit we have to do that ourselves... */
         if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0)
@@ -2770,6 +2839,34 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void
                        format_timespan(t, sizeof(t), s->watchdog_usec, 1));
 
         service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG);
+        return 0;
+}
+
+static int service_dispatch_exit_on_idle_timer(sd_event_source *source, usec_t usec, void *userdata) {
+        Service *s = SERVICE(userdata);
+        nsec_t usage;
+        int r;
+
+        assert(s);
+        assert(source == s->exit_on_idle_event_source);
+
+        r = unit_get_cpu_usage(UNIT(s), &usage);
+        if (r < 0) {
+                log_unit_error_errno(UNIT(s)->id, r, "%s failed to get cpu usage: %m", UNIT(s)->id);
+                return 0;
+        }
+
+        if (usage == UNIT(s)->cpuacct_usage) {
+                log_unit_info(UNIT(s)->id, "%s is in idle state, stopping...", UNIT(s)->id);
+                r = manager_add_job(UNIT(s)->manager, JOB_STOP, UNIT(s), JOB_FAIL, true, NULL, NULL);
+                if (r < 0) {
+                        log_unit_error_errno(UNIT(s)->id, r, "%s failed to enqueue stopping job: %m", UNIT(s)->id);
+                        return r;
+                }
+        } else {
+                UNIT(s)->cpuacct_usage = usage;
+                service_reset_exit_on_idle_timer(s);
+        }
 
         return 0;
 }
diff --git a/src/core/service.h b/src/core/service.h
index 7da0a93..909908e 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -212,6 +212,11 @@ struct Service {
         ServiceFDStore *fd_store;
         unsigned n_fd_store;
         unsigned n_fd_store_max;
+
+        bool exit_on_idle;
+        dual_timestamp exit_on_idle_timestamp;
+        usec_t exit_on_idle_usec;
+        sd_event_source *exit_on_idle_event_source;
 };
 
 extern const UnitVTable service_vtable;
diff --git a/src/core/unit.h b/src/core/unit.h
index 11242c2..84c0cd0 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -178,6 +178,7 @@ struct Unit {
 
         /* Where the cpuacct.usage cgroup counter was at the time the unit was started */
         nsec_t cpuacct_usage_base;
+        nsec_t cpuacct_usage;
 
         /* Counterparts in the cgroup filesystem */
         char *cgroup_path;
-- 
1.9.3



More information about the systemd-devel mailing list