From 953c347fb6f293acbd6da009646bfc071b68ddd7 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 23 Sep 2025 14:28:33 +0200 Subject: [PATCH 1/4] test: rename TEST-53-ISSUE-16347 to TEST-53-TIMER And split the existing test into a separate subtest. --- .../meson.build | 0 test/integration-tests/meson.build | 2 +- ...3-ISSUE-16347.sh => TEST-53-TIMER.issue-16347.sh} | 12 ++++-------- test/units/TEST-53-TIMER.sh | 11 +++++++++++ 4 files changed, 16 insertions(+), 9 deletions(-) rename test/integration-tests/{TEST-53-ISSUE-16347 => TEST-53-TIMER}/meson.build (100%) rename test/units/{TEST-53-ISSUE-16347.sh => TEST-53-TIMER.issue-16347.sh} (82%) create mode 100755 test/units/TEST-53-TIMER.sh diff --git a/test/integration-tests/TEST-53-ISSUE-16347/meson.build b/test/integration-tests/TEST-53-TIMER/meson.build similarity index 100% rename from test/integration-tests/TEST-53-ISSUE-16347/meson.build rename to test/integration-tests/TEST-53-TIMER/meson.build diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index f28be9e24e..5965f6646c 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -70,7 +70,7 @@ foreach dirname : [ 'TEST-46-HOMED', 'TEST-50-DISSECT', 'TEST-52-HONORFIRSTSHUTDOWN', - 'TEST-53-ISSUE-16347', + 'TEST-53-TIMER', 'TEST-54-CREDS', 'TEST-55-OOMD', 'TEST-58-REPART', diff --git a/test/units/TEST-53-ISSUE-16347.sh b/test/units/TEST-53-TIMER.issue-16347.sh similarity index 82% rename from test/units/TEST-53-ISSUE-16347.sh rename to test/units/TEST-53-TIMER.issue-16347.sh index 84cd66129d..8b266145cd 100755 --- a/test/units/TEST-53-ISSUE-16347.sh +++ b/test/units/TEST-53-TIMER.issue-16347.sh @@ -3,10 +3,9 @@ set -eux set -o pipefail -: >/failed - # Reset host date to current time, 3 days in the past. date -s "-3 days" +trap 'date -s "+3 days"' EXIT # Run a timer for every 15 minutes. systemd-run --unit test-timer --on-calendar "*:0/15:0" true @@ -17,15 +16,12 @@ 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)) || { +if [[ "$time_delta" -lt 0 || "$time_delta" -gt 1200 ]]; then 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 + echo -if test ! -s /failed ; then - rm -f /failed - touch /testok + exit 1 fi diff --git a/test/units/TEST-53-TIMER.sh b/test/units/TEST-53-TIMER.sh new file mode 100755 index 0000000000..9c2a033aa9 --- /dev/null +++ b/test/units/TEST-53-TIMER.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok From 5730a400fd5ee82566fe03eb832121a0d4bc26b6 Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Tue, 23 Sep 2025 17:42:01 +0200 Subject: [PATCH 2/4] test: restarting elapsed timer shouldn't trigger the corresponding service Provides coverage for: - https://github.com/systemd/systemd/issues/31231 - https://github.com/systemd/systemd/issues/35805 --- test/units/TEST-53-TIMER.restart-trigger.sh | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 test/units/TEST-53-TIMER.restart-trigger.sh diff --git a/test/units/TEST-53-TIMER.restart-trigger.sh b/test/units/TEST-53-TIMER.restart-trigger.sh new file mode 100755 index 0000000000..057f379ddc --- /dev/null +++ b/test/units/TEST-53-TIMER.restart-trigger.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Restarting an already elapsed timer shouldn't immediately trigger the corresponding service unit. +# +# Provides coverage for: +# - https://github.com/systemd/systemd/issues/31231 +# - https://github.com/systemd/systemd/issues/35805 +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/util.sh + +UNIT_NAME="timer-restart-$RANDOM" +TEST_MESSAGE="Hello from timer $RANDOM" + +# Setup +cat >"/run/systemd/system/$UNIT_NAME.timer" <"/run/systemd/system/$UNIT_NAME.service" <"/run/systemd/system/$UNIT_NAME.timer.d/99-override.conf" < Date: Tue, 23 Sep 2025 21:04:12 +0200 Subject: [PATCH 3/4] test: check the next elapse timer timestamp after deserialization When deserializing a serialized timer unit with RandomizedDelaySec= set, systemd should use the last inactive exit timestamp instead of current realtime to calculate the new next elapse, so the timer unit actually runs in the given calendar window. Provides coverage for: - https://github.com/systemd/systemd/issues/18678 - https://github.com/systemd/systemd/pull/27752 --- ...TEST-53-TIMER.RandomizedDelaySec-reload.sh | 97 +++++++++++++++++++ test/units/util.sh | 9 ++ 2 files changed, 106 insertions(+) create mode 100755 test/units/TEST-53-TIMER.RandomizedDelaySec-reload.sh diff --git a/test/units/TEST-53-TIMER.RandomizedDelaySec-reload.sh b/test/units/TEST-53-TIMER.RandomizedDelaySec-reload.sh new file mode 100755 index 0000000000..08f4f469d6 --- /dev/null +++ b/test/units/TEST-53-TIMER.RandomizedDelaySec-reload.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# When deserializing a serialized timer unit with RandomizedDelaySec= set, systemd should use the last +# inactive exit timestamp instead of current realtime to calculate the new next elapse, so the timer unit +# actually runs in the given calendar window. +# +# Provides coverage for: +# - https://github.com/systemd/systemd/issues/18678 +# - https://github.com/systemd/systemd/pull/27752 +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/util.sh + +UNIT_NAME="timer-RandomizedDelaySec-$RANDOM" +TARGET_TS="$(date --date="tomorrow 00:10")" +TARGET_TS_S="$(date --date="$TARGET_TS" "+%s")" +# Maximum possible next elapse timestamp: $TARGET_TS (OnCalendar=) + 22 hours (RandomizedDelaySec=) +MAX_NEXT_ELAPSE_REALTIME_S="$((TARGET_TS_S + 22 * 60 * 60))" +MAX_NEXT_ELAPSE_REALTIME="$(date --date="@$MAX_NEXT_ELAPSE_REALTIME_S")" + +# Let's make sure to return the date & time back to the original state once we're done with our time +# shenigans. One way to do this would be to use hwclock, but the RTC in VMs can be unreliable or slow to +# respond, causing unexpected test fails/timeouts. +# +# Instead, let's save the realtime timestamp before we start with the test together with a current monotonic +# timestamp, after the test ends take the difference between the current monotonic timestamp and the "start" +# one, add it to the originally saved realtime timestamp, and finally use that timestamp to set the system +# time. This should advance the system time by the amount of time the test actually ran, and hence restore it +# to some sane state after the time jumps performed by the test. It won't be perfect, but it should be close +# enough for our needs. +START_REALTIME="$(date "+%s")" +START_MONOTONIC="$(cut -d . -f 1 /proc/uptime)" +at_exit() { + : "Restore the system date to a sane state" + END_MONOTONIC="$(cut -d . -f 1 /proc/uptime)" + date --set="@$((START_REALTIME + END_MONOTONIC - START_MONOTONIC))" +} +trap at_exit EXIT + +# Set some predictable time so we can schedule the first timer elapse in a deterministic-ish way +date --set="23:00" + +# Setup +cat >"/run/systemd/system/$UNIT_NAME.timer" <"/run/systemd/system/$UNIT_NAME.service" <&2 + exit 1 + fi +)} + assert_in() {( set +ex From 3fc44a0f68412b649e16f12ff2f97a36c615457d Mon Sep 17 00:00:00 2001 From: Lukas Nykryn Date: Tue, 9 Sep 2025 15:24:22 +0200 Subject: [PATCH 4/4] timer: don't run service immediately after restart of a timer When a timer is restarted, don't reset the last_trigger field. This prevents the timer from triggering immediately. Fixes: #31231 --- src/core/timer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/timer.c b/src/core/timer.c index a45e0c393f..2e0a8a17c3 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -664,8 +664,6 @@ static int timer_start(Unit *u) { if (r < 0) return r; - t->last_trigger = DUAL_TIMESTAMP_NULL; - /* Reenable all timers that depend on unit activation time */ LIST_FOREACH(value, v, t->values) if (v->base == TIMER_ACTIVE)