Add OnTransactionFinishedSec= setting for timers that allows for timers to fire at a specific interval after the transaction in which they were started has completed.
This is specifically useful for running services after boot. For example, having a timer with OnTransactionFinishedSec=0 and enabling it in default.target will case the timer to start as soon as the system has reached default.target, but not before. Signed-off-by: Christian Seiler <[email protected]> --- man/systemd.timer.xml | 17 +++++++++++-- src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/manager.c | 21 +++++++++++++++ src/core/manager.h | 5 ++++ src/core/timer.c | 48 ++++++++++++++++++++++++++++++----- src/core/timer.h | 9 +++++++ 6 files changed, 92 insertions(+), 9 deletions(-) diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml index 6fc26a5..c20e21a 100644 --- a/man/systemd.timer.xml +++ b/man/systemd.timer.xml @@ -104,6 +104,7 @@ <term><varname>OnStartupSec=</varname></term> <term><varname>OnUnitActiveSec=</varname></term> <term><varname>OnUnitInactiveSec=</varname></term> + <term><varname>OnTransactionFinishedSec=</varname></term> <listitem><para>Defines timers relative to different starting points: @@ -122,7 +123,10 @@ activated. <varname>OnUnitInactiveSec=</varname> defines a timer relative to when the unit the timer is activating was last - deactivated.</para> + deactivated. <varname>OnTransactionFinishedSec=</varname> + defines a timer relative to the end + of the transaction in which the timer + was started.</para> <para>Multiple directives may be combined of the same and of different @@ -152,7 +156,16 @@ elapse and the configured unit is started. This is not the case for timers defined in the other - directives.</para></listitem> + directives.</para> + + <para>The <varname>OnTransactionFinishedSec=</varname> + setting is useful for starting a timer + after the system finished booting (in + contrast to <varname>OnBootSec=</varname>, + which defines timers relative to the + point the system started booting) if the + timer is enabled in + <filename>default.target</filename>.</para></listitem> <para>These are monotonic timers, independent of wall-clock time and timezones. If the diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 8187cd4..f68fa12 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -235,6 +235,7 @@ Timer.OnBootSec, config_parse_timer, 0, Timer.OnStartupSec, config_parse_timer, 0, 0 Timer.OnUnitActiveSec, config_parse_timer, 0, 0 Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 +Timer.OnTransactionFinishedSec, config_parse_timer, 0, 0 Timer.Unit, config_parse_timer_unit, 0, 0 m4_dnl Path.PathExists, config_parse_path_spec, 0, 0 diff --git a/src/core/manager.c b/src/core/manager.c index 3cd9915..78e3856 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -461,6 +461,7 @@ static void manager_clear_jobs_and_units(Manager *m) { assert(!m->dbus_job_queue); assert(!m->cleanup_queue); assert(!m->gc_queue); + assert(!m->transaction_timer_queue); assert(hashmap_isempty(m->jobs)); assert(hashmap_isempty(m->units)); @@ -944,6 +945,23 @@ unsigned manager_dispatch_dbus_queue(Manager *m) { return n; } +unsigned manager_dispatch_transaction_timer_queue(Manager *m) { + Timer *t; + unsigned n = 0; + usec_t finish_time; + + finish_time = now(CLOCK_MONOTONIC); + + while ((t = m->transaction_timer_queue)) { + assert(t->in_transaction_timer_queue); + + timer_notify_transaction_finished(t, finish_time); + n++; + } + + return n; +} + static int manager_process_notify_fd(Manager *m) { ssize_t n; @@ -2027,6 +2045,9 @@ void manager_check_finished(Manager *m) { /* Notify Type=idle units that we are done now */ close_pipe(m->idle_pipe); + /* Recalculate all timers that are based on end-of-transaction times */ + manager_dispatch_transaction_timer_queue(m); + /* Turn off confirm spawn now */ m->confirm_spawn = false; diff --git a/src/core/manager.h b/src/core/manager.h index 913752f..d9d4785 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -34,6 +34,7 @@ typedef struct Manager Manager; typedef enum WatchType WatchType; typedef struct Watch Watch; +typedef struct Timer Timer; typedef enum ManagerExitCode { MANAGER_RUNNING, @@ -113,6 +114,9 @@ struct Manager { LIST_HEAD(Unit, dbus_unit_queue); LIST_HEAD(Job, dbus_job_queue); + /* Timers to ping after transaction is done */ + LIST_HEAD(Timer, transaction_timer_queue); + /* Units to remove */ LIST_HEAD(Unit, cleanup_queue); @@ -262,6 +266,7 @@ void manager_clear_jobs(Manager *m); unsigned manager_dispatch_load_queue(Manager *m); unsigned manager_dispatch_run_queue(Manager *m); unsigned manager_dispatch_dbus_queue(Manager *m); +unsigned manager_dispatch_transaction_timer_queue(Manager *m); int manager_set_default_controllers(Manager *m, char **controllers); int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); diff --git a/src/core/timer.c b/src/core/timer.c index 7080b32..16749ba 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -43,6 +43,7 @@ static void timer_init(Unit *u) { assert(u->load_state == UNIT_STUB); t->next_elapse = (usec_t) -1; + t->last_transaction_finished = (usec_t) -1; } static void timer_done(Unit *u) { @@ -58,6 +59,9 @@ static void timer_done(Unit *u) { unit_unwatch_timer(u, &t->timer_watch); + if (t->in_transaction_timer_queue) + LIST_REMOVE(Timer, transaction_timer_queue, u->manager->transaction_timer_queue, t); + unit_ref_unset(&t->unit); } @@ -166,7 +170,7 @@ static void timer_set_state(Timer *t, TimerState state) { unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); } -static void timer_enter_waiting(Timer *t, bool initial); +static void timer_enter_waiting(Timer *t, bool initial, bool transaction_just_finished); static int timer_coldplug(Unit *u) { Timer *t = TIMER(u); @@ -177,7 +181,7 @@ static int timer_coldplug(Unit *u) { if (t->deserialized_state != t->state) { if (t->deserialized_state == TIMER_WAITING) - timer_enter_waiting(t, false); + timer_enter_waiting(t, false, false); else timer_set_state(t, t->deserialized_state); } @@ -194,7 +198,7 @@ static void timer_enter_dead(Timer *t, TimerResult f) { timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); } -static void timer_enter_waiting(Timer *t, bool initial) { +static void timer_enter_waiting(Timer *t, bool initial, bool transaction_just_finished) { TimerValue *v; usec_t base = 0, delay, n; bool found = false; @@ -241,6 +245,24 @@ static void timer_enter_waiting(Timer *t, bool initial) { base = UNIT_DEREF(t->unit)->inactive_enter_timestamp.monotonic; break; + case TIMER_TRANSACTION_FINISHED: + + if (t->last_transaction_finished == (usec_t) -1) { + if (initial) { + LIST_PREPEND(Timer, transaction_timer_queue, UNIT(t)->manager->transaction_timer_queue, t); + t->in_transaction_timer_queue = true; + } + continue; + } + + base = t->last_transaction_finished; + + /* if we are called the first time after a transaction just finished, + * ensure that the event will be considered (initial = false!) */ + if (transaction_just_finished && base + v->value < n) + base = n - v->value; + break; + default: assert_not_reached("Unknown timer base"); } @@ -312,7 +334,8 @@ static int timer_start(Unit *u) { return -ENOENT; t->result = TIMER_SUCCESS; - timer_enter_waiting(t, true); + t->last_transaction_finished = (usec_t) -1; + timer_enter_waiting(t, true, false); return 0; } @@ -425,14 +448,14 @@ void timer_unit_notify(Unit *u, UnitActiveState new_state) { case TIMER_ELAPSED: /* Recalculate sleep time */ - timer_enter_waiting(t, false); + timer_enter_waiting(t, false, false); break; case TIMER_RUNNING: if (UNIT_IS_INACTIVE_OR_FAILED(new_state)) { log_debug("%s got notified about unit deactivation.", UNIT(t)->id); - timer_enter_waiting(t, false); + timer_enter_waiting(t, false, false); } break; @@ -447,6 +470,16 @@ void timer_unit_notify(Unit *u, UnitActiveState new_state) { } } +void timer_notify_transaction_finished(Timer *t, usec_t finish_time) +{ + t->last_transaction_finished = finish_time; + if (t->in_transaction_timer_queue) { + LIST_REMOVE(Timer, transaction_timer_queue, UNIT(t)->manager->transaction_timer_queue, t); + t->in_transaction_timer_queue = false; + } + timer_enter_waiting(t, false, true); +} + static void timer_reset_failed(Unit *u) { Timer *t = TIMER(u); @@ -473,7 +506,8 @@ static const char* const timer_base_table[_TIMER_BASE_MAX] = { [TIMER_BOOT] = "OnBootSec", [TIMER_STARTUP] = "OnStartupSec", [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", - [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec" + [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", + [TIMER_TRANSACTION_FINISHED] = "OnTransactionFinishedSec" }; DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); diff --git a/src/core/timer.h b/src/core/timer.h index c6d1d42..94a4213 100644 --- a/src/core/timer.h +++ b/src/core/timer.h @@ -41,6 +41,7 @@ typedef enum TimerBase { TIMER_STARTUP, TIMER_UNIT_ACTIVE, TIMER_UNIT_INACTIVE, + TIMER_TRANSACTION_FINISHED, _TIMER_BASE_MAX, _TIMER_BASE_INVALID = -1 } TimerBase; @@ -66,6 +67,7 @@ struct Timer { Unit meta; LIST_HEAD(TimerValue, values); + usec_t last_transaction_finished; usec_t next_elapse; TimerState state, deserialized_state; @@ -74,10 +76,17 @@ struct Timer { Watch timer_watch; TimerResult result; + + /* Transaction timer queue */ + LIST_FIELDS(Timer, transaction_timer_queue); + + bool in_transaction_timer_queue:1; }; void timer_unit_notify(Unit *u, UnitActiveState new_state); +void timer_notify_transaction_finished(Timer *t, usec_t finish_time); + extern const UnitVTable timer_vtable; const char *timer_state_to_string(TimerState i); -- 1.7.11.4 _______________________________________________ systemd-devel mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/systemd-devel
