]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
timer: Adjust calendar timers based on monotonic timer instead of realtime
authorFilipe Brandenburger <filbranden@gmail.com>
Fri, 10 Jul 2020 21:24:00 +0000 (14:24 -0700)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 15 Jul 2020 07:23:09 +0000 (09:23 +0200)
When the RTC time at boot is off in the future by a few days, OnCalendar=
timers will be scheduled based on the time at boot. But if the time has been
adjusted since boot, the timers will end up scheduled way in the future, which
may cause them not to fire as shortly or often as expected.

Update the logic so that the time will be adjusted based on monotonic time.
We do that by calculating the adjusted manager startup realtime from the
monotonic time stored at that time, by comparing that time with the realtime
and monotonic time of the current time.

Added a test case to validate this works as expected. The test case creates a
QEMU virtual machine with the clock 3 days in the future. Then we adjust the
clock back 3 days, and test creating a timer with an OnCalendar= for every 15
minutes. We also check the manager startup timestamp from both `systemd-analyze
dump` and from D-Bus.

Test output without the corresponding code changes that fix the issue:

  Timer elapse outside of the expected 20 minute window.
    next_elapsed=1594686119
    now=1594426921
    time_delta=259198

With the code changes in, the test passes as expected.

src/core/timer.c
test/TEST-53-ISSUE-16347/Makefile [new symlink]
test/TEST-53-ISSUE-16347/test.sh [new file with mode: 0755]
test/units/testsuite-53.service [new file with mode: 0644]
test/units/testsuite-53.sh [new file with mode: 0755]

index 7f779fb936b5471a4e039dcbf740a0bac47eb790..75f1dc1f8beb3f11bcd530af5598778535b4d9a1 100644 (file)
@@ -346,6 +346,7 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
         bool found_monotonic = false, found_realtime = false;
         bool leave_around = false;
         triple_timestamp ts;
+        dual_timestamp dts;
         TimerValue *v;
         Unit *trigger;
         int r;
@@ -389,10 +390,10 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
 
                         /* To make the delay due to RandomizedDelaySec= work even at boot,
                          * if the scheduled time has already passed, set the time when systemd
-                         * first started as the scheduled time.
-                         * Also, we don't have to check t->persistent since the logic implicitly express true. */
-                        if (v->next_elapse < UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].realtime)
-                                v->next_elapse = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].realtime;
+                         * first started as the scheduled time. */
+                        dual_timestamp_from_monotonic(&dts, UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic);
+                        if (v->next_elapse < dts.realtime)
+                                v->next_elapse = dts.realtime;
 
                         if (!found_realtime)
                                 t->next_elapse_realtime = v->next_elapse;
diff --git a/test/TEST-53-ISSUE-16347/Makefile b/test/TEST-53-ISSUE-16347/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-53-ISSUE-16347/test.sh b/test/TEST-53-ISSUE-16347/test.sh
new file mode 100755 (executable)
index 0000000..089768e
--- /dev/null
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+set -e
+
+TEST_DESCRIPTION="test timer units when initial clock is ahead"
+TEST_NO_NSPAWN=1
+
+future_date=$(date -u +%Y-%m-%dT%H:%M:%S -d '+3 days')
+QEMU_OPTIONS="-rtc base=${future_date}"
+. $TEST_BASE_DIR/test-functions
+
+do_test "$@" 53
diff --git a/test/units/testsuite-53.service b/test/units/testsuite-53.service
new file mode 100644 (file)
index 0000000..d4dd8cc
--- /dev/null
@@ -0,0 +1,7 @@
+[Unit]
+Description=TEST-53-ISSUE-16347
+
+[Service]
+ExecStartPre=rm -f /failed /testok
+ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
+Type=oneshot
diff --git a/test/units/testsuite-53.sh b/test/units/testsuite-53.sh
new file mode 100755 (executable)
index 0000000..3536c24
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+set -ex
+set -o pipefail
+
+>/failed
+
+# Reset host date to current time, 3 days in the past.
+date -s "-3 days"
+
+# Run a timer for every 15 minutes.
+systemd-run --unit test-timer --on-calendar "*:0/15:0" true
+
+next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
+next_elapsed=$(date -d "${next_elapsed}" +%s)
+now=$(date +%s)
+time_delta=$((next_elapsed - now))
+
+# Check that the timer will elapse in less than 20 minutes.
+((0 < time_delta && time_delta < 1200)) || {
+    echo 'Timer elapse outside of the expected 20 minute window.'
+    echo "  next_elapsed=${next_elapsed}"
+    echo "  now=${now}"
+    echo "  time_delta=${time_delta}"
+    echo ''
+} >>/failed
+
+if test ! -s /failed ; then
+    rm -f /failed
+    touch /testok
+fi