Files
systemd/src/core/dbus-timer.c
Arthur Shau cc0ab8c810 timer: introduce DeferReactivation setting
By default, in instances where timers are running on a realtime schedule,
if a service takes longer to run than the interval of a timer, the
service will immediately start again when the previous invocation finishes.
This is caused by the fact that the next elapse is calculated based on
the last trigger time, which, combined with the fact that the interval
is shorter than the runtime of the service, causes that elapse to be in
the past, which in turn means the timer will trigger as soon as the
service finishes running.

This behavior can be changed by enabling the new DeferReactivation setting,
which will cause the next calendar elapse to be calculated based on when
the trigger unit enters inactivity, rather than the last trigger time.

Thus, if a timer is on an realtime interval, the trigger will always
adhere to that specified interval.
E.g. if you have a timer that runs on a minutely interval, the setting
guarantees that triggers will happen at *:*:00 times, whereas by default
this may skew depending on how long the service runs.

Co-authored-by: Matteo Croce <teknoraver@meta.com>
2024-10-11 22:54:16 +02:00

369 lines
13 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "bus-get-properties.h"
#include "dbus-timer.h"
#include "dbus-util.h"
#include "strv.h"
#include "timer.h"
#include "unit.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, timer_result, TimerResult);
static int property_get_monotonic_timers(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Timer *t = ASSERT_PTR(userdata);
int r;
assert(bus);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "(stt)");
if (r < 0)
return r;
LIST_FOREACH(value, v, t->values) {
_cleanup_free_ char *usec = NULL;
if (v->base == TIMER_CALENDAR)
continue;
usec = timer_base_to_usec_string(v->base);
if (!usec)
return -ENOMEM;
r = sd_bus_message_append(reply, "(stt)", usec, v->value, v->next_elapse);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_calendar_timers(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Timer *t = ASSERT_PTR(userdata);
int r;
assert(bus);
assert(reply);
r = sd_bus_message_open_container(reply, 'a', "(sst)");
if (r < 0)
return r;
LIST_FOREACH(value, v, t->values) {
_cleanup_free_ char *buf = NULL;
if (v->base != TIMER_CALENDAR)
continue;
r = calendar_spec_to_string(v->calendar_spec, &buf);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "(sst)", timer_base_to_string(v->base), buf, v->next_elapse);
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_next_elapse_monotonic(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
Timer *t = ASSERT_PTR(userdata);
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "t", timer_next_elapse_monotonic(t));
}
const sd_bus_vtable bus_timer_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Unit", "s", bus_property_get_triggered_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
SD_BUS_PROPERTY("OnClockChange", "b", bus_property_get_bool, offsetof(Timer, on_clock_change), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OnTimezoneChange", "b", bus_property_get_bool, offsetof(Timer, on_timezone_change), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Timer, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("AccuracyUSec", "t", bus_property_get_usec, offsetof(Timer, accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Timer, random_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FixedRandomDelay", "b", bus_property_get_bool, offsetof(Timer, fixed_random_delay), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Persistent", "b", bus_property_get_bool, offsetof(Timer, persistent), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("WakeSystem", "b", bus_property_get_bool, offsetof(Timer, wake_system), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemainAfterElapse", "b", bus_property_get_bool, offsetof(Timer, remain_after_elapse), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DeferReactivation", "b", bus_property_get_bool, offsetof(Timer, defer_reactivation), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
static int timer_add_one_monotonic_spec(
Timer *t,
const char *name,
TimerBase base,
UnitWriteFlags flags,
usec_t usec,
sd_bus_error *error) {
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
TimerValue *v;
unit_write_settingf(UNIT(t), flags|UNIT_ESCAPE_SPECIFIERS, name,
"%s=%s",
timer_base_to_string(base),
FORMAT_TIMESPAN(usec, USEC_PER_MSEC));
v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
*v = (TimerValue) {
.base = base,
.value = usec,
};
LIST_PREPEND(value, t->values, v);
}
return 1;
}
static int timer_add_one_calendar_spec(
Timer *t,
const char *name,
TimerBase base,
UnitWriteFlags flags,
const char *str,
sd_bus_error *error) {
_cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
int r;
r = calendar_spec_from_string(str, &c);
if (r == -EINVAL)
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid calendar spec");
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
unit_write_settingf(UNIT(t), flags|UNIT_ESCAPE_SPECIFIERS, name,
"%s=%s", timer_base_to_string(base), str);
TimerValue *v = new(TimerValue, 1);
if (!v)
return -ENOMEM;
*v = (TimerValue) {
.base = base,
.calendar_spec = TAKE_PTR(c),
};
LIST_PREPEND(value, t->values, v);
}
return 1;
};
static int bus_timer_set_transient_property(
Timer *t,
const char *name,
sd_bus_message *message,
UnitWriteFlags flags,
sd_bus_error *error) {
Unit *u = UNIT(t);
int r;
assert(t);
assert(name);
assert(message);
flags |= UNIT_PRIVATE;
if (streq(name, "AccuracyUSec"))
return bus_set_transient_usec(u, name, &t->accuracy_usec, message, flags, error);
if (streq(name, "AccuracySec")) {
log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead.");
return bus_set_transient_usec(u, "AccuracyUSec", &t->accuracy_usec, message, flags, error);
}
if (streq(name, "RandomizedDelayUSec"))
return bus_set_transient_usec(u, name, &t->random_usec, message, flags, error);
if (streq(name, "FixedRandomDelay"))
return bus_set_transient_bool(u, name, &t->fixed_random_delay, message, flags, error);
if (streq(name, "WakeSystem"))
return bus_set_transient_bool(u, name, &t->wake_system, message, flags, error);
if (streq(name, "Persistent"))
return bus_set_transient_bool(u, name, &t->persistent, message, flags, error);
if (streq(name, "RemainAfterElapse"))
return bus_set_transient_bool(u, name, &t->remain_after_elapse, message, flags, error);
if (streq(name, "OnTimezoneChange"))
return bus_set_transient_bool(u, name, &t->on_timezone_change, message, flags, error);
if (streq(name, "OnClockChange"))
return bus_set_transient_bool(u, name, &t->on_clock_change, message, flags, error);
if (streq(name, "DeferReactivation"))
return bus_set_transient_bool(u, name, &t->defer_reactivation, message, flags, error);
if (streq(name, "TimersMonotonic")) {
const char *base_name;
usec_t usec;
bool empty = true;
r = sd_bus_message_enter_container(message, 'a', "(st)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(st)", &base_name, &usec)) > 0) {
TimerBase b;
b = timer_base_from_string(base_name);
if (b < 0 || b == TIMER_CALENDAR)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid timer base: %s", base_name);
r = timer_add_one_monotonic_spec(t, name, b, flags, usec, error);
if (r < 0)
return r;
empty = false;
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && empty) {
timer_free_values(t);
unit_write_setting(u, flags, name, "OnActiveSec=");
}
return 1;
} else if (streq(name, "TimersCalendar")) {
const char *base_name, *str;
bool empty = true;
r = sd_bus_message_enter_container(message, 'a', "(ss)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(ss)", &base_name, &str)) > 0) {
TimerBase b;
b = timer_base_from_string(base_name);
if (b != TIMER_CALENDAR)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid timer base: %s", base_name);
r = timer_add_one_calendar_spec(t, name, b, flags, str, error);
if (r < 0)
return r;
empty = false;
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && empty) {
timer_free_values(t);
unit_write_setting(u, flags, name, "OnCalendar=");
}
return 1;
} else if (STR_IN_SET(name,
"OnActiveSec",
"OnBootSec",
"OnStartupSec",
"OnUnitActiveSec",
"OnUnitInactiveSec")) {
TimerBase b;
usec_t usec;
log_notice("Client is using obsolete %s= transient property, please use TimersMonotonic= instead.", name);
b = timer_base_from_string(name);
if (b < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown timer base %s", name);
r = sd_bus_message_read(message, "t", &usec);
if (r < 0)
return r;
return timer_add_one_monotonic_spec(t, name, b, flags, usec, error);
} else if (streq(name, "OnCalendar")) {
const char *str;
log_notice("Client is using obsolete %s= transient property, please use TimersCalendar= instead.", name);
r = sd_bus_message_read(message, "s", &str);
if (r < 0)
return r;
return timer_add_one_calendar_spec(t, name, TIMER_CALENDAR, flags, str, error);
}
return 0;
}
int bus_timer_set_property(
Unit *u,
const char *name,
sd_bus_message *message,
UnitWriteFlags mode,
sd_bus_error *error) {
Timer *t = TIMER(u);
assert(t);
assert(name);
assert(message);
if (u->transient && u->load_state == UNIT_STUB)
return bus_timer_set_transient_property(t, name, message, mode, error);
return 0;
}