]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
timer: introduce DeferReactivation setting
authorArthur Shau <arthurshau@meta.com>
Thu, 14 Mar 2024 19:43:13 +0000 (12:43 -0700)
committerMatteo Croce <teknoraver@meta.com>
Fri, 11 Oct 2024 20:54:16 +0000 (22:54 +0200)
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>
docs/TRANSIENT-SETTINGS.md
man/org.freedesktop.systemd1.xml
man/systemd.timer.xml
src/core/dbus-timer.c
src/core/load-fragment-gperf.gperf.in
src/core/timer.c
src/core/timer.h
src/shared/bus-unit-util.c
test/fuzz/fuzz-unit-file/directives-all.service
test/fuzz/fuzz-unit-file/tmpfiles-clean.timer

index 15f1cbc47c4eae1c18c2cd66e2c96282f62068ab..e219131ce61f99288ec6f50bc0a6c93c467285cc 100644 (file)
@@ -387,6 +387,7 @@ Most timer unit settings are available to transient units.
 ✓ AccuracySec=
 ✓ RandomizedDelaySec=
 ✓ FixedRandomDelay=
+✓ DeferReactivation=
   Unit=
 ```
 
index ccc3e25fbcaabd0ad405f1f5bf59ed5346de5b33..1e34ddbc857aa3a6d2c3535740e5fb6cf24adc58 100644 (file)
@@ -8804,6 +8804,8 @@ node /org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dclean_2etimer {
       readonly b WakeSystem = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
       readonly b RemainAfterElapse = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly b DeferReactivation = ...;
   };
   interface org.freedesktop.DBus.Peer { ... };
   interface org.freedesktop.DBus.Introspectable { ... };
@@ -8832,6 +8834,8 @@ node /org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dclean_2etimer {
 
     <!--property RemainAfterElapse is not documented!-->
 
+    <!--property DeferReactivation is not documented!-->
+
     <!--Autogenerated cross-references for systemd.directives, do not edit-->
 
     <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Unit"/>
@@ -8874,6 +8878,8 @@ node /org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dclean_2etimer {
 
     <variablelist class="dbus-property" generated="True" extra-ref="RemainAfterElapse"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="DeferReactivation"/>
+
     <!--End of Autogenerated section-->
 
     <refsect2>
@@ -12386,5 +12392,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <title>Job Objects</title>
       <para><varname>ActivationDetails</varname> was added in version 252.</para>
     </refsect2>
+    <refsect2>
+      <title>Timer Objects</title>
+      <para><varname>DeferReactivation</varname> was added in version 257.</para>
+    </refsect2>
   </refsect1>
 </refentry>
index ad79552ed66dcd904d5a12cd4c33daab109c994f..2ea56d687fa6021b8826cb6e1620aece95b2882a 100644 (file)
         <xi:include href="version-info.xml" xpointer="v247"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>DeferReactivation=</varname></term>
+
+        <listitem><para>Takes a boolean argument. When enabled, the timer schedules the next elapse based on
+        the trigger unit entering inactivity, instead of the last trigger time.
+        This is most apparent in the case where the service unit takes longer to run than the timer interval.
+        With this setting enabled, the timer will schedule the next elapse based on when the service finishes
+        running, and so it will have to wait until the next realtime elapse time to trigger.
+        Otherwise, the default behavior is for the timer unit to immediately trigger again once the service
+        finishes running. This happens because the timer schedules the next elapse based on the previous trigger
+        time, and since the interval is shorter than the service runtime, that elapse will be in the past,
+        causing it to immediately trigger once done.</para>
+
+        <para>This setting has no effect if a realtime timer has not been specified with
+        <varname>OnCalendar=</varname>. Defaults to <option>false</option>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>OnClockChange=</varname></term>
         <term><varname>OnTimezoneChange=</varname></term>
index 4f78a521de2ab9c17f59dd7dbba76f73a4afe8d6..b9d0c16acd87580b40043492a795323c73062b98 100644 (file)
@@ -118,6 +118,7 @@ const sd_bus_vtable bus_timer_vtable[] = {
         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
 };
 
@@ -233,6 +234,9 @@ static int bus_timer_set_transient_property(
         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;
index 221099a39c01143d664c66dfc2e0aee189ad3c4b..e94b518a9d8fc10d053b53d4eb051ba22f8e5562 100644 (file)
@@ -570,6 +570,7 @@ Timer.Persistent,                        config_parse_bool,
 Timer.WakeSystem,                        config_parse_bool,                           0,                                  offsetof(Timer, wake_system)
 Timer.RemainAfterElapse,                 config_parse_bool,                           0,                                  offsetof(Timer, remain_after_elapse)
 Timer.FixedRandomDelay,                  config_parse_bool,                           0,                                  offsetof(Timer, fixed_random_delay)
+Timer.DeferReactivation,                 config_parse_bool,                           0,                                  offsetof(Timer, defer_reactivation)
 Timer.AccuracySec,                       config_parse_sec,                            0,                                  offsetof(Timer, accuracy_usec)
 Timer.RandomizedDelaySec,                config_parse_sec,                            0,                                  offsetof(Timer, random_usec)
 Timer.Unit,                              config_parse_trigger_unit,                   0,                                  0
index 7cb58cc2d9d6e6921b308a71666d8b311c1faa73..e44298d5e84456d4afd3079bf56a0cafc37cdaf7 100644 (file)
@@ -245,7 +245,8 @@ static void timer_dump(Unit *u, FILE *f, const char *prefix) {
                 "%sRemainAfterElapse: %s\n"
                 "%sFixedRandomDelay: %s\n"
                 "%sOnClockChange: %s\n"
-                "%sOnTimeZoneChange: %s\n",
+                "%sOnTimeZoneChange: %s\n"
+                "%sDeferReactivation: %s\n",
                 prefix, timer_state_to_string(t->state),
                 prefix, timer_result_to_string(t->result),
                 prefix, trigger ? trigger->id : "n/a",
@@ -255,7 +256,8 @@ static void timer_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, yes_no(t->remain_after_elapse),
                 prefix, yes_no(t->fixed_random_delay),
                 prefix, yes_no(t->on_clock_change),
-                prefix, yes_no(t->on_timezone_change));
+                prefix, yes_no(t->on_timezone_change),
+                prefix, yes_no(t->defer_reactivation));
 
         LIST_FOREACH(value, v, t->values)
                 if (v->base == TIMER_CALENDAR) {
@@ -391,12 +393,19 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
                 if (v->base == TIMER_CALENDAR) {
                         usec_t b, rebased;
 
-                        /* If we know the last time this was
-                         * triggered, schedule the job based relative
-                         * to that. If we don't, just start from
-                         * the activation time. */
+                        /* If DeferReactivation= is enabled, schedule the job based on the last time
+                         * the trigger unit entered inactivity. Otherwise, if we know the last time
+                         * this was triggered, schedule the job based relative to that. If we don't,
+                         * just start from the activation time or realtime. */
 
-                        if (dual_timestamp_is_set(&t->last_trigger))
+                        if (t->defer_reactivation &&
+                            dual_timestamp_is_set(&trigger->inactive_enter_timestamp)) {
+                                if (dual_timestamp_is_set(&t->last_trigger))
+                                        b = MAX(trigger->inactive_enter_timestamp.realtime,
+                                                t->last_trigger.realtime);
+                                else
+                                        b = trigger->inactive_enter_timestamp.realtime;
+                        } else if (dual_timestamp_is_set(&t->last_trigger))
                                 b = t->last_trigger.realtime;
                         else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
                                 b = UNIT(t)->inactive_exit_timestamp.realtime;
index 2624001c34bb562411f287a759721e42045c670f..14a9931dffe5775092a215fd46d9519febbb9dc9 100644 (file)
@@ -61,6 +61,7 @@ struct Timer {
         bool on_clock_change;
         bool on_timezone_change;
         bool fixed_random_delay;
+        bool defer_reactivation;
 
         char *stamp_path;
 };
index 9fdf56620522b54fc1742e4a0a911b83824780ef..b15182692088689dd631d73385c03871e7d294ec 100644 (file)
@@ -2594,7 +2594,8 @@ static int bus_append_timer_property(sd_bus_message *m, const char *field, const
                               "Persistent",
                               "OnTimezoneChange",
                               "OnClockChange",
-                              "FixedRandomDelay"))
+                              "FixedRandomDelay",
+                              "DeferReactivation"))
                 return bus_append_parse_boolean(m, field, eq);
 
         if (STR_IN_SET(field, "AccuracySec",
index dbd56ec752fda748b065c8b6c600d617cd42c23a..1cb212bcadd6728cf2c6fe2913a23d626968dfc9 100644 (file)
@@ -7,6 +7,7 @@ AllowedCPUs=
 AllowedMemoryNodes=
 AllowIsolate=
 Also=
+DeferReactivation=
 AmbientCapabilities=
 AssertACPower=
 AssertArchitecture=
index 5bf91b9f4cd13bd6ae16b2ea472db86ea7e4a23c..5dc269243faebb0dc58fddc1a43770d0dce75f78 100644 (file)
@@ -33,6 +33,7 @@ Persistent=true
 AccuracySec=24h
 RandomizedDelaySec=234234234
 FixedRandomDelay=true
+DeferReactivation=true
 
 Persistent=no
 Unit=foo.service