Fedora Account System
Red Hat Associate
Red Hat Customer
On a dual-boot machine with the RTC kept in local time (timedatectl set-local-rtc 1, the usual Windows dual-boot setup), the system clock is sometimes off by exactly the UTC offset (3 hours here) after resuming from s2idle. chronyd detects the offset. Because the default Fedora config only allows stepping during the first 3 updates (makestep 1.0 3), chronyd slews the clock instead. The slew slows the kernel clock by about 8.3% (adjtimex tick = 9167 instead of 10000). CLOCK_MONOTONIC then runs at 0.917x real speed for about 36 hours, until the offset is corrected. Everything timed against CLOCK_MONOTONIC runs slow in that period. The most visible effect is that the KDE Plasma (KWin, Wayland) desktop becomes slow and choppy: animations, window moves and the cursor are all affected. A reboot fixes it because chronyd is allowed to step the clock again at startup. A second, unrelated laptop (Huawei, 13th-gen Intel, no dGPU) on Fedora 44 KDE shows the same symptom. Versions: chrony-4.9-1.fc44.x86_64 systemd-259.9-1.fc44.x86_64 kernel-7.2.7-200.fc44.x86_64 kwin / plasma-workspace 6.7.5-1.fc44 Reproducible: Sometimes Steps to Reproduce: 1. Keep the RTC in local time with a non-UTC timezone (here Asia/Nicosia, UTC+3): sudo timedatectl set-local-rtc 1 2. Boot, wait for chronyd to synchronise, then suspend (s2idle) and resume, repeatedly and with longer sleeps. 3. After a bad resume, run: journalctl | grep "System clock wrong by" and chronyc tracking Actual Results: Journal after resume: 14:45:04 systemd-logind: Lid opened. 14:45:05 kernel: PM: suspend exit 14:45:15 chronyd[1511]: System clock wrong by -10799.997000 seconds chronyc tracking a few minutes later: System time : 10681.260742188 seconds fast of NTP time adjtimex() shows tick 9167. CLOCK_MONOTONIC / CLOCK_MONOTONIC_RAW measured over 5 s = 0.91668. The desktop stays laggy until "sudo chronyc makestep" or a reboot. After makestep the ratio returns to 1.00001 and the lag disappears immediately. Expected Results: The clock should not be off by the UTC offset after resume. If an offset of several hours does appear after resume, chronyd should step it immediately instead of slewing it over ~36 hours while the whole system runs 8% slow. Additional Information: - The offset is exactly the UTC offset (10800 s). This suggests the local-time RTC is being read as UTC somewhere on the resume path, or local time is being written to the RTC while the kernel assumes UTC. I could not confirm which. If this belongs to the kernel (timekeeping / rtc-cmos) rather than chrony, please reassign. - Workarounds: "sudo timedatectl set-local-rtc 0" plus RealTimeIsUniversal=1 on Windows, or "makestep 1.0 -1" in /etc/chrony.conf. - Possible improvements: have chronyd step large offsets that appear right after resume; document the local-RTC interaction in the Fedora chrony.conf comments. - Ruled out while the desktop was lagging: CPU/GPU load, thermal/PROCHOT throttling, i915 errors, PSR (not supported by the panel), timer latency on all CPUs (median < 0.2 ms), clocksource (tsc), KWin renderer (hardware OpenGL). - Hardware: Lenovo ThinkBook 16p G5 (i9-14900HX, Intel UHD iGPU, NVIDIA dGPU disabled), mem_sleep = s2idle only. - Full write-up, raw measurements and a read-only check script: https://github.com/EOLO03/kde-suspend-lag-rtc-fix - Upstream discussion: https://discussion.fedoraproject.org/t/203245
Created attachment 2159072 [details] Raw measurements from the affected machine Attaching the raw measurements referenced in the description: timedatectl/chronyc/adjtimex output before and after the fix, and the list of ruled-out causes with measured values.
Correction: the second laptop runs GNOME, not KDE, and I also saw this earlier on my own machine under GNOME. So it's not KDE-specific.
The default chrony configuration doesn't allow steps after the first three update to prevent upsetting services and applications sensitive to clock steps. The problem is with the RTC and/or the kernel. Either the RTC is not being set by the kernel (11-minute mode) at all or with an incorrect timezone offset, or the reads it back with an incorrect offset. Please check that your /etc/chrony.conf includes the "rtcsync" directive. Before suspending, please run timedatectl to verify if the RTC is set to the correct time.
Thanks for the explanation. 1. Yes, /etc/chrony.conf includes "rtcsync" (unmodified Fedora default). 2. I had switched to RTC in UTC as a workaround, so I've now switched back to local time (timedatectl set-local-rtc 1) to reproduce. Right after switching, timedatectl shows the RTC set correctly: Local time: Wed 2026-09-30 14:38:33 EEST Universal time: Wed 2026-09-30 11:38:33 UTC RTC time: Wed 2026-09-30 14:38:33 RTC in local TZ: yes 3. I installed a systemd-sleep hook that logs, right before suspend and right after resume: system time (UTC and local), the raw RTC (/sys/class/rtc/rtc0/date, time, since_epoch), chronyc tracking and the adjtimex tick/status. I'll post the log after a few suspend/resume cycles, including at least one long sleep. One thing I noticed: with RTC in local time, timedatectl warns "The RTC time is never updated, it relies on external facilities to maintain it", and earlier adjtimex showed status 0x40 (STA_UNSYNC). Could this mean the kernel 11-minute mode doesn't update the RTC in this configuration?
It seems timedated always logs that message when the RTC is in local time. adjtimex should not have 0x40 (STA_UNSYNC) when chronyd is running and updating the clock with the rtcsync directive.
Thanks. To clarify when I saw STA_UNSYNC: it was during the bad state, about 30 minutes after the resume, while chronyd was running with rtcsync and slewing the ~10681 s offset. At that time: adjtimex: status 0x40, tick 9167 timedatectl: System clock synchronized: no chronyc tracking: Reference ID A29FC801 (time.cloudflare.com), Stratum 4, System time 10681.26 seconds fast of NTP time, Leap status Normal Right now, with the RTC back in local time and no suspend since, everything looks normal: status 0x0, tick 10000, "System clock synchronized: yes". The sleep hook also records the adjtimex status right before suspend and right after resume, so the next log will show whether STA_UNSYNC was already set before suspending or only appeared after resume.
Ok, with a large uncorrected offset it is expected to see the STA_UNSYNC flag. The maximum value of the adjtimex maxerror is 16 seconds and with chronyd that includes the uncorrected offset.
Reproduced on Oct 1 with RTC in local time. My sleep hook did not run (I had put it in /etc/systemd/system-sleep instead of /usr/lib/systemd/system-sleep), so I don't have the pre-suspend RTC reading yet. It is fixed now and I'll post that log next. Here is what the journal and the state after resume show. Timeline (real times derived from NTP; lid closed the whole time): 20:21:32 suspend entry (s2idle) 20:43:35 resume, clock OK "PM: suspend devices took 0.115 seconds" 20:43:47 suspend entry again (KDE re-suspends 11 s later, lid still closed) ~20:48:29 resume, journal shows 23:48:29 -> clock jumped +3 h here "PM: suspend devices took 1.568 seconds", "PM: resume devices took 2.292 seconds" 23:48:39 suspend entry 23:59:47 resume 00:00:13 chronyd: Forward time jump detected! 00:00:18 chronyd: System clock wrong by -10799.704534 seconds State about one minute later: Local time: Fri 2026-10-02 00:01:05 EEST Universal time: Thu 2026-10-01 21:01:05 UTC RTC time: Thu 2026-10-01 21:01:09 RTC in local TZ: yes /sys/class/rtc/rtc0: 2026-10-01 21:01:09 chronyc tracking: System time 10795.439 seconds fast of NTP time adjtimex: status 0x2040, tick 9167 /etc/adjtime: LOCAL The real local time at that moment was 21:01, so the RTC held the correct local time, and the system clock was equal to the RTC value interpreted as UTC. So the RTC itself looks fine; the system time after that resume is what the RTC would give if read without the local-time offset. The first resume (22 min sleep) was fine; the jump happened on the resume that followed a re-suspend 11 s after the previous resume. The bad cycle also has much longer device suspend/resume times (1.57 s / 2.29 s vs 0.115 s). Other details that may matter: - At boot the kernel does the usual: "rtc_cmos rtc_cmos: setting system clock to ... UTC", then systemd "RTC configured in localtime, applying delta of 180 minutes to system time". - Kernel 7.2.7 also registers a second RTC: "acpi-tad ACPI000E:00: registered as rtc1". /sys/bus/platform/devices/ACPI000E:00/time reads 2026:10:1:21:4:26:2047:0 (same local time, TZ field 2047 = unspecified). - Clocksource is tsc. Boot log has "[Firmware Bug]: TSC ADJUST differs within socket(s), fixing all errors". Side observation, possibly a separate issue: on Sep 30 I ran "timedatectl set-local-rtc 1" at runtime (RTC was correct local time right after). After about 7 hours of uptime I rebooted, and at that boot the RTC held UTC ("PM: RTC time: 18:15:18" while local time was 21:15), so systemd's 180-minute delta made the clock 3 h slow and chronyd logged "System clock wrong by 10800.307774 seconds" and stepped it. Given that the RTC is correct and the jump happens inside the kernel resume path, should this be reassigned to the kernel?
Created attachment 2159642 [details] systemd-sleep hook log: system time, raw RTC and boottime before/after each suspend
I now have before/after data from the sleep hook for two occurrences (Oct 1 on kernel 7.2.7, Oct 3 on 7.2.8). The full log is attached (35 suspend cycles). Columns: system time (UTC), raw rtc0 time, their difference, and the change across the suspend in system time, RTC time and CLOCK_BOOTTIME (/proc/uptime), all in seconds. phase system UTC rtc0 raw rtc-sys d_sys d_rtc d_boottime pre 2026-10-01 19:00:38 2026-10-01 22:00:37 10799 post 2026-10-01 19:09:34 2026-10-01 22:09:33 10799 536 536 536.0 pre 2026-10-01 19:09:44 2026-10-01 22:09:43 10799 post 2026-10-01 23:29:56 2026-10-01 23:29:56 0 15612 4813 15611.9 <- jump pre 2026-10-01 23:30:07 2026-10-01 23:30:07 0 post 2026-10-02 00:58:35 2026-10-02 00:58:35 0 5308 5308 5308.6 ... pre 2026-10-03 13:22:59 2026-10-03 16:22:59 10800 post 2026-10-03 13:23:06 2026-10-03 16:23:05 10799 7 6 6.4 pre 2026-10-03 13:23:16 2026-10-03 16:23:16 10800 post 2026-10-03 16:23:20 2026-10-03 16:23:21 1 10804 5 10803.8 <- jump pre 2026-10-03 16:23:31 2026-10-03 16:23:32 1 post 2026-10-03 16:23:38 2026-10-03 16:23:39 1 7 7 7.2 What this shows: 1. Before the bad suspend the RTC is correct: it holds local time, 10800 s ahead of system UTC. 2. The RTC is not changed across the suspend; it advances by the real sleep time (4813 s and 5 s). 3. System time AND CLOCK_BOOTTIME both advance by the sleep time plus 10800 s. So the extra 3 h was injected by the kernel as sleep time; it was not set from userspace. 4. Afterwards system UTC equals the raw RTC value, and no further jump is visible while they are equal. It happened in 2 of 35 logged cycles (9 of those were while the clock was already shifted, where it could not show). Before the Oct 3 jump the machine was waking and re-suspending every ~17 s with the lid closed. This looks like rtc_resume() in drivers/rtc/class.c injecting (RTC - system time) as sleep time. As far as I can tell from the source: - On x86 rtc_suspend() returns early because timekeeping_rtc_skipsuspend() is true (persistent clock exists), so old_rtc and old_system stay zero. - In s2idle, timekeeping_suspend()/timekeeping_resume() run for each wakeup inside the s2idle loop. If the last iteration is shorter than the 1 s resolution of the CMOS clock, timekeeping_resume() injects nothing (no nonstop clocksource, persistent clock not strictly greater), so suspend_timing_needed stays true. - rtc_resume() is then not skipped and computes (new_rtc - old_rtc) - (new_system - old_system) with the old values at zero, i.e. new_rtc - new_system, and injects it when it is >= 0. With the RTC in UTC that value is about zero and invisible. With the RTC in local time east of UTC it is the UTC offset. If this reading is right, it would not happen west of UTC (negative value, not injected). I have not confirmed it with tracing, but it matches all three numbers above. So this does not look like a chrony problem. Could you reassign it to the kernel, or should I file a new bug there?
Following up on the earlier comment: the jump is reproducible on demand now, the cause is in the kernel, and a patch is on the list. Patch: https://lore.kernel.org/all/20261003163138.14221-1-sabri.alperen03@gmail.com/ ("rtc: class: Do not inject sleep time without a suspend snapshot", sent to linux-rtc with the timekeeping maintainers in Cc) Cause, in short: - rtc_suspend() returns early when timekeeping_rtc_skipsuspend() is true, i.e. whenever a persistent clock exists. On x86 that is always, so old_rtc and old_system stay zero. - rtc_resume() is skipped under a different condition, timekeeping_rtc_skipresume(), which is only true after timekeeping_resume() has injected sleep time. - With s2idle, timekeeping_suspend()/timekeeping_resume() run for every pass through the idle loop. When the last pass freezes timekeeping for less than a second, the CMOS persistent clock has not advanced past timekeeping_suspend_time, nothing is injected, and rtc_resume() runs. - With the old values being zero it injects new_rtc - new_system. With the RTC in UTC that is about zero. With the RTC in local time east of UTC it is the UTC offset. Reproducer: the script stops chronyd, shifts the system clock by less than a second relative to the RTC, suspends to idle with a wake alarm 3 to 6 seconds ahead, and puts a kprobe with a stack trace on timekeeping_inject_sleeptime64(). 8 control cycles without the shift, then 30 test cycles. Same machine as before (ThinkBook 16p G5, i9-14900HX, s2idle only, RTC in local time, UTC+3). kernel control jumps test jumps 7.2.8-200.fc44 0 of 8 7 of 30 7.3.0-rc5 unpatched 0 of 8 4 of 30 7.3.0-rc5 patched 0 of 8 0 of 30 Every jump is 10797 to 10799 seconds and the kprobe stack is always timekeeping_inject_sleeptime64 rtc_resume dpm_run_callback device_resume dpm_resume dpm_resume_end suspend_devices_and_enter The trace clock was set to x86-tsc, which keeps counting in s2idle, so the trace shows how long timekeeping was really frozen. In every cycle that jumped, the longest freeze was below 0.9 s. In every cycle that did not jump, there was either a freeze of 0.99 s or more, or no freeze at all. On the patched kernel 6 cycles had only sub-0.9 s freezes and none of them jumped. The script, the three logs and the traces are at https://github.com/EOLO03/kde-suspend-lag-rtc-fix/tree/main/kernel-repro So chrony is not at fault here. It only reacts to a clock that the kernel moved. Could the component be changed to kernel?
Thanks for the detailed report and upstream patch submission. I'm reassigning this bug to the kernel component.