You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Clock change detected. Flushing caches and stop DNS to work in the WSL #761
Adding the mechanism behind this symptom, traced on an affected box (Docker Desktop 4.77.0, engine 29.5.3; also observed by the reporter above on 4.67.0). Short version: the 30-second clock step is a userspace settimeofday(2) call made by Docker Desktop's own init process (/initd) inside the docker-desktop distro — not WSL's Hyper-V TimeSync path, and not a kernel bug. Because all of a user's WSL2 distros share one utility VM and one CLOCK_REALTIME, the step lands on every co-resident distro, which is why systemd-resolved in an unrelated Ubuntu distro flushes its DNS cache twice a minute.
1. Causation is reversible (A→B→A)
Counting clock steps with the same watcher systemd-resolved arms (timerfd + TFD_TIMER_CANCEL_ON_SET), toggling only Docker Desktop:
Docker Desktop UP 4 steps / 100 s @ 30.002 s (2.40/min)
Docker Desktop DOWN 0 steps / 130 s (0.00/min) <- DockerCli.exe -Shutdown
Docker Desktop UP 3 steps / 100 s @ 30.001 s (1.80/min) <- restarted
2. The transport is a userspace syscall from initd
bpftrace on the shared WSL2 kernel while the steps fired:
The kernel Hyper-V TimeSync setter (hv_set_host_time, the function that would call do_settimeofday64 on a host-initiated sync) fired zero times — which also explains why hv_utils.timesync_implicit=0 does not help: the steps never enter that code path.
A follow-up capture joined each call to its argument and effect: five consecutive settimeofday(2) calls from comm=initd, each requesting a backward step (kernel-side capture of the timespec64 argument), each returning 0, each matched 1:1 with a TFD_TIMER_CANCEL_ON_SET cancellation — the per-event delta agreeing to microseconds with an independent realtime-vs-monotonic watcher, and hv_set_host_time again at zero throughout.
3. initd ships in Docker Desktop's distro, and nowhere else on this box
In the docker-desktop distro (PRETTY_NAME="Docker Desktop"), PID 1 is init(docker-desktop) and the processes with comm=initd resolve to the /initd binary. No initd exists in the affected Ubuntu distro (its init is systemd; the WSL core init is /init, comm init).
4. One VM, one clock
Both distros report the sameboot_id and the same uptime — one utility VM, one CLOCK_REALTIME. A step made in the docker-desktop distro is a step in every distro.
5. Magnitude: ~1 ms
The steps measure at ~0.2–2.5 ms each (typically ~1.2–1.7 ms), always backward — two independent instruments (kernel-side capture of the requested timespec64 and userspace realtime-vs-monotonic differencing) agree per event to microseconds. These are tiny corrections applied as hard clock steps, every 30 s, for as long as Docker Desktop is running (observed across three days).
Suggested fix
Periodic guest-clock correction is legitimate (WSL2 guests genuinely drift across Windows sleep — cf. microsoft/WSL#10006). The problem is stepping rather than slewing:
Slew small deltas via adjtimex (gradual ADJ_OFFSET slewing — not ADJ_SETOFFSET, which is itself a step) instead of settimeofday. A slew is not a CLOCK_REALTIME discontinuity and does not wake TFD_TIMER_CANCEL_ON_SET watchers — no cache flush, no log spam. This is exactly what the systemd maintainer prescribed when the consumer half of this was reported there (resolved: on_clock_change() flushes the entire DNS cache on any CLOCK_REALTIME step, with no magnitude check systemd/systemd#43028, closed not-our-bug: "if wsl makes continious small steps then they really should use the gradual clock changes (adjtimex) instead of the stepping ones (clock_settime)").
Or gate on magnitude: only hard-step when the delta is large enough to matter (post-resume drift is seconds to minutes), and skip or slew when the guest is already within a few ms — as it was in every measurement here.
Either change stops the flush loop in every co-resident distro while keeping real post-resume corrections (a large step after actual sleep would still step, correctly).
Reproduction
# 1. Cadence (no root): arm an absolute CLOCK_REALTIME timerfd (TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET) and# count ECANCELED wakeups — steps land every ~30 s while Docker Desktop runs.# 2. Transport (root, bpftrace):
sudo bpftrace -e ' kprobe:hv_set_host_time { @host_path = count(); } kprobe:do_settimeofday64 { @by[comm] = count(); @stk[kstack(5)] = count(); } interval:s:95 { exit(); }'# Expect: @host_path absent/0 ; @by[initd] > 0 ; stack through __x64_sys_settimeofday.# 3. Causation: "DockerCli.exe -Shutdown" -> steps stop; restart Docker Desktop -> steps return.
Environment: Windows 10 (19045-class) host, WSL2 kernel 6.6.114.1-microsoft-standard-WSL2, Ubuntu 24.04 (systemd 255.4) co-resident with docker-desktop; reproduced 2026-07-14 and re-confirmed 2026-07-16/17 on Docker Desktop 4.77.0.
Two honest boundaries: (1) I did not disassemble /initd, so I can't say why it re-steps every 30 s rather than on demand — though the joined trace confirms each call writes a fresh backward ~1 ms correction and succeeds — and the fix direction (slew or magnitude-gate) holds regardless. (2) This trace explains the cache-flush loop and the log spam specifically; whether flushing alone accounts for every network symptom reported upstream is for the thread to judge.
Cross-references: microsoft/WSL#13777 (same symptom, correctly correlated with Docker Desktop by its reporters), systemd/systemd#43028 (the resolved-side half, closed with "report to the stepper").
Re-confirmed on current Docker Desktop, and adding the intake data this report's fields were left
blank for.
Still reproducing on 4.89.0
Measured today (2026-09-17) with the same instrument as my July capture — an absolute CLOCK_REALTIME timerfd armed TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, counting cancellations,
alongside an independent realtime-vs-monotonic watcher:
3 CLOCK_REALTIME steps in 100 s
at t = 13.165, 43.161, 73.167 s
intervals = 29.996 s, 30.006 s
offsets = -1.978 ms, -2.182 ms, -1.427 ms
Unchanged from July in cadence (30.00 s), direction (backward) and magnitude (~1–2 ms), on a Docker
Desktop two minor versions newer.
Full trace in #761 (comment). In short: the
30-second step is a userspace settimeofday(2) call from Docker Desktop's /initd inside the docker-desktop distro, attributed under bpftrace to comm=initd, with hv_set_host_time — the
Hyper-V TimeSync setter — at zero calls throughout the capture. Because these co-resident distros
share one utility VM and one CLOCK_REALTIME, the step lands in every distro on the box.
Because the observed calls are userspace settimeofday rather than the Hyper-V path, hv_utils.timesync_implicit=0 does not address this particular mechanism — that parameter governs
implicit host synchronisation, not userspace callers.
Suggested fix
Unchanged: slew small deltas via adjtimex gradual ADJ_OFFSET rather than stepping (note ADJ_SETOFFSET is itself a step, so not every adjtimex mode qualifies), or gate the hard step on
magnitude so a genuine post-resume correction still steps while a ~1 ms one does not. A slew is not a CLOCK_REALTIME discontinuity and does not cancel TFD_TIMER_CANCEL_ON_SET watchers.
For reference, the systemd maintainers closed the consumer-side report (systemd/systemd#43028) as
not-their-bug, recommending gradual adjustment over stepping. They attributed the stepping to WSL and
asked for it to be reported there; the attribution to Docker Desktop's /initd is from my later
trace, not theirs. The magnitude-gating option is my suggestion, not part of their recommendation.
Scope
I traced the successful syscalls and their effects; I did not inspect /initd's implementation, so I
can't say why it re-steps every 30 s rather than on demand. This explains the periodic clock steps
and the cache-flush messages they cause. It does not establish the cause of every DNS or connectivity
failure reported in the WSL thread — @mihalt reported DNS problems there that persisted after Docker
Desktop was removed.
Happy to supply a bundle, a bpftrace capture, or a test-build result.
Docker Desktop's init process in the docker-desktop WSL2 distro appears to step the shared WSL2 VM clock backwards roughly every 30 s via settimeofday(). This looks like the same cause as the reports above (4.67.0, 4.77.0, 4.89.0); it is also reproducible on 4.95.0.
Reported observations (from a trace taken in an Ubuntu 24.04 WSL2 distro with kernel ftrace syscall tracepoints):
settimeofday() is called every ~29.97 s, each time by a short-lived "initd" process with a new pid
it is the only syscall that steps the clock (no clock_settime in the traced window)
recent steps are about -30 ms each (earlier comments in this issue report 1-2.5 ms)
each step matches one "Clock change detected. Flushing caches." entry from systemd-resolved, and journald logs "Time jumped backwards"
a chronyd process in the init pid namespace calls clock_adjtime about every 8 s (reported, not independently confirmed)
the steps stop when Docker Desktop is fully quit and return when it restarts
Reproduce
Windows 11 with WSL 2.7.11 and Ubuntu 24.04 with systemd enabled
Start Docker Desktop 4.95.0 (WSL2 backend)
In Ubuntu run: journalctl -fu systemd-resolved
Watch "Clock change detected. Flushing caches." appear every ~30 s
Fully quit Docker Desktop and the messages stop
Expected behavior
Small clock corrections are slewed (adjtime/adjtimex) instead of stepped, or small offsets are skipped, so other WSL2 distros don't see clock jumps. Alternatively, an option to disable Docker Desktop's own time sync.
docker version
Docker Desktop 4.95.0 (build 242831), engine/CLI 29.8.2
(full docker version output: not provided)
docker info
(not provided; redact host-specific details before posting)
Diagnostics ID
(add only if the reporter chooses to share it)
Additional Info
WSL 2.7.11.0, kernel 6.18.33.2-microsoft-standard-WSL2, Windows 11 build 10.0.26300.9711. Clock offset vs Windows stays under ~0.3 s. Ruled out: Docker Desktop restart, stopping systemd-timesyncd, wsl --shutdown, Windows reboot, fixing w32time, hv_utils.timesync_implicit=0.
Still reproducing on Docker Desktop 4.95.0 (build 242831, engine 29.8.2), WSL 2.7.11, Windows 10.0.26300.9711, Ubuntu 24.04 distro. Also reported to Docker Support, who asked me to bring the data here.
Symptom (unchanged)
systemd-resolved in Ubuntu logs Clock change detected. Flushing caches. every 30 s (121 times in the last hour) and drops its whole DNS cache each time. journald logs Time jumped backwards. If you fully quit Docker Desktop, the flushes stop at once. They come back when you start it again.
New data points vs. the earlier comments here
Kernel ftrace syscall tracepoints, enabled from the Ubuntu distro (all distros share one kernel, so docker-desktop processes are visible):
Every call comes from a short-lived initd child with a new pid, every 29.97 s, so /initd seems to fork a helper on a 30 s timer. settimeofday is the only syscall that steps the clock (no clock_settime).
The steps can be much larger than the 1–2 ms seen here before. I measured −5 to −8 ms on 2026-10-18 and −29 to −32 ms on 2026-10-19 (CLOCK_REALTIME − CLOCK_MONOTONIC sampled every 20 ms).
A chronyd (pid 111 in the init pid namespace, not visible from Ubuntu or docker-desktop) calls clock_adjtime every 8 s. The tick wanders between 10000 and 10190 (normal is 10000). That may be why the guest clock keeps drifting between the /initd corrections. Resetting the tick by hand is reverted within seconds.
Ruled out
Stopping systemd-timesyncd, restarting Docker Desktop, wsl --shutdown, rebooting Windows, fixing Windows w32time (now synced to an NTP pool), and hv_utils.timesync_implicit=0 on the kernel command line. None of them stopped the 30 s steps. Only quitting Docker Desktop does.
Suggested fixes
Slew small offsets (adjtime/clock_adjtime with ADJ_OFFSET) instead of stepping with settimeofday.
Only step above a threshold (e.g. > 1 s, like chrony's makestep), which would cover real host sleep/resume.
Or make it configurable / let it be turned off when the WSL system distro already disciplines the clock.
A Diagnostics ID gathered during the stepping has been shared with Docker Support.
Full ftrace output (settimeofday / clock_adjtime / adjtimex, 95 s)
Syscall trace of the clock stepping
Captured 2026-10-19 ~15:04-15:06 local time (UTC+02:00) on the affected host, while Docker Desktop 4.95.0 was running.
Method: kernel ftrace syscall tracepoints, enabled from the Ubuntu-24.04 distro (all WSL2 distros share one kernel,
so this sees processes of docker-desktop and of the WSL system distro too). Events: settimeofday (enter/exit),
clock_settime, clock_adjtime, adjtimex. Duration 95 s. Timestamps in column 4 are ftrace's local clock (seconds).
== 1. settimeofday calls only ==
initd-22110 [004] ..... 67179.004188: sys_settimeofday(tv: 0x2c8157c6dc38, tz: 0)
initd-22110 [004] ..... 67179.004636: sys_settimeofday -> 0x0
initd-22374 [004] ..... 67208.975387: sys_settimeofday(tv: 0x2c8156a1fc38, tz: 0)
initd-22374 [004] .N... 67208.975500: sys_settimeofday -> 0x0
initd-22338 [002] ..... 67238.946063: sys_settimeofday(tv: 0x2c8155edfc38, tz: 0)
initd-22338 [002] .N... 67238.946177: sys_settimeofday -> 0x0
Notes:
- each call comes from a short-lived 'initd' process with a NEW pid every time (22110, 22374, 22338):
/initd seems to fork a helper every ~30 s (intervals between calls: 29.97 s and 29.97 s);
- no clock_settime calls at all in the window; settimeofday is the only stepping syscall.
- systemd-timesyncd (pid 498, Ubuntu) reacts ~95 ms after each step with clock_adjtime;
- a chronyd (pid 111 in the init pid namespace, not visible from Ubuntu or docker-desktop) calls
clock_adjtime 4-5 times every 8 s - this is likely the tick/freq adjuster mentioned in the report.
== 2. Step size measured right after the trace (CLOCK_REALTIME - CLOCK_MONOTONIC, sampled every 20 ms) ==
2026-10-19 15:06:31 UTC+0200 step -31.89 ms
2026-10-19 15:07:01 UTC+0200 step -29.00 ms
(Today's steps are about -30 ms; on 2026-10-18 they were -5 to -8 ms.)
== 3. systemd-resolved in Ubuntu-24.04 over the same period (one flush per step) ==
2026-10-19T15:04:01+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:04:31+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:05:01+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:05:31+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:06:01+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:06:31+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
2026-10-19T15:07:01+02:00 host systemd-resolved[320]: Clock change detected. Flushing caches.
== 4. Full raw ftrace output ==
# tracer: nop
#
# entries-in-buffer/entries-written: 58/58 #P:5
#
# _-----=> irqs-off/BH-disabled
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / _-=> migrate-disable
# |||| / delay
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
# | | | ||||| | |
chronyd-111 [003] ..... 67171.502589: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [003] ..... 67171.502657: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [003] ..... 67171.502662: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [003] ..... 67171.502667: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
initd-22110 [004] ..... 67179.004188: sys_settimeofday(tv: 0x2c8157c6dc38, tz: 0)
initd-22110 [004] ..... 67179.004636: sys_settimeofday -> 0x0
systemd-timesyn-498 [003] ..... 67179.098817: sys_clock_adjtime(which_clock: 0, utx: 0x7ffc4ddec660)
chronyd-111 [003] ..... 67179.529557: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [003] ..... 67179.529568: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [003] ..... 67179.529572: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [003] ..... 67179.529596: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [001] ..... 67187.442904: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c226b0)
chronyd-111 [001] ..... 67187.550674: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [001] ..... 67187.550684: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [001] ..... 67187.550689: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [001] ..... 67187.550692: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [004] ..... 67195.551907: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [004] ..... 67195.551920: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [004] ..... 67195.551925: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [004] ..... 67195.551930: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [002] ..... 67203.546586: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [002] ..... 67203.546641: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [002] ..... 67203.546646: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [002] ..... 67203.546650: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
initd-22374 [004] ..... 67208.975387: sys_settimeofday(tv: 0x2c8156a1fc38, tz: 0)
initd-22374 [004] .N... 67208.975500: sys_settimeofday -> 0x0
systemd-timesyn-498 [001] ..... 67209.072148: sys_clock_adjtime(which_clock: 0, utx: 0x7ffc4ddec660)
chronyd-111 [000] ..... 67211.580357: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [000] ..... 67211.580404: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [000] ..... 67211.580409: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [000] ..... 67211.580413: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [000] ..... 67219.595076: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [000] ..... 67219.595087: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [000] ..... 67219.595091: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [000] ..... 67219.595096: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [004] ..... 67227.593858: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [004] ..... 67227.593869: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [004] ..... 67227.593873: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [004] ..... 67227.593877: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [004] ..... 67235.588933: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [004] ..... 67235.588945: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [004] ..... 67235.588949: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [004] ..... 67235.588953: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
initd-22338 [002] ..... 67238.946063: sys_settimeofday(tv: 0x2c8155edfc38, tz: 0)
initd-22338 [002] .N... 67238.946177: sys_settimeofday -> 0x0
systemd-timesyn-498 [003] ..... 67239.041777: sys_clock_adjtime(which_clock: 0, utx: 0x7ffc4ddec660)
chronyd-111 [002] ..... 67243.629358: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [002] ..... 67243.629369: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [002] ..... 67243.629374: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [002] ..... 67243.629379: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [004] ..... 67251.637413: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [004] ..... 67251.637433: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [004] ..... 67251.637440: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [004] ..... 67251.637446: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
chronyd-111 [000] ..... 67259.636352: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c225d0)
chronyd-111 [000] ..... 67259.636363: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22250)
chronyd-111 [000] ..... 67259.636368: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22260)
chronyd-111 [000] ..... 67259.636371: sys_clock_adjtime(which_clock: 0, utx: 0x7ffcc5c22350)
Still reproducing after updating to Docker Desktop 4.96.0 (build 243774, engine 29.9.1, released today), WSL 2.7.11, Windows 10.0.26300.9711, Ubuntu 24.04 distro.
State on 4.96.0 (measured 2026-10-21, ~10 minutes after the update)
systemd-resolved still logs Clock change detected. Flushing caches every 30.0 s (e.g. 17:27:15.692, 17:27:45.692, 17:28:15.689, 17:28:45.684 UTC), right from Docker Desktop's restart, so the DNS cache stays useless in every WSL distro.
CLOCK_REALTIME − CLOCK_MONOTONIC sampled every 50 ms for 92 s: one backward step every 30 s, of −8.7 ms, −7.8 ms, −8.5 ms (smaller than the ~−30 ms I saw on 4.95.0 two days ago, but still a step rather than a slew).
ftrace of syscall entry tracepoints for 65 s:
settimeofday: 2 calls, 29.99 s apart, both from initd (one from a long-lived initd process, one from a short-lived initd child; on 4.95.0 every call came from a new short-lived child);
clock_adjtime: 32 calls from chronyd (≈ every 2 s, versus every 8 s on 4.95.0) and 2 from systemd-timesyncd.
So the update did not change the behaviour: initd still steps the shared clock every 30 s, while chronyd (WSL system distro) now disciplines it more often. Both keep fighting, and every initd step triggers the cache flush in all distros.
Still suggested
Slew small offsets (clock_adjtime with ADJ_OFFSET) instead of settimeofday.
Or only step above a threshold (e.g. > 1 s), which still covers host sleep/resume.
Or make it configurable / skip it when the WSL system distro's chronyd already disciplines the clock.
Activity
ttk commentedon Apr 4, 2026
Only when docker desktop is running,
Clock change detected. Flushing cachesmessage is generated every 30 seconds in the WSLsyslog.Running
Ubuntu 24.04and Docker Desktop 4.67.0.0xdhx commentedon Jul 17, 2026
Adding the mechanism behind this symptom, traced on an affected box (Docker Desktop 4.77.0, engine 29.5.3; also observed by the reporter above on 4.67.0). Short version: the 30-second clock step is a userspace
settimeofday(2)call made by Docker Desktop's own init process (/initd) inside thedocker-desktopdistro — not WSL's Hyper-V TimeSync path, and not a kernel bug. Because all of a user's WSL2 distros share one utility VM and oneCLOCK_REALTIME, the step lands on every co-resident distro, which is whysystemd-resolvedin an unrelated Ubuntu distro flushes its DNS cache twice a minute.1. Causation is reversible (A→B→A)
Counting clock steps with the same watcher
systemd-resolvedarms (timerfd+TFD_TIMER_CANCEL_ON_SET), toggling only Docker Desktop:2. The transport is a userspace syscall from
initdbpftraceon the shared WSL2 kernel while the steps fired:The kernel Hyper-V TimeSync setter (
hv_set_host_time, the function that would calldo_settimeofday64on a host-initiated sync) fired zero times — which also explains whyhv_utils.timesync_implicit=0does not help: the steps never enter that code path.A follow-up capture joined each call to its argument and effect: five consecutive
settimeofday(2)calls fromcomm=initd, each requesting a backward step (kernel-side capture of thetimespec64argument), each returning 0, each matched 1:1 with aTFD_TIMER_CANCEL_ON_SETcancellation — the per-event delta agreeing to microseconds with an independent realtime-vs-monotonic watcher, andhv_set_host_timeagain at zero throughout.3.
initdships in Docker Desktop's distro, and nowhere else on this boxIn the
docker-desktopdistro (PRETTY_NAME="Docker Desktop"), PID 1 isinit(docker-desktop)and the processes withcomm=initdresolve to the/initdbinary. Noinitdexists in the affected Ubuntu distro (its init is systemd; the WSL core init is/init, comminit).4. One VM, one clock
Both distros report the same
boot_idand the same uptime — one utility VM, oneCLOCK_REALTIME. A step made in thedocker-desktopdistro is a step in every distro.5. Magnitude: ~1 ms
The steps measure at ~0.2–2.5 ms each (typically ~1.2–1.7 ms), always backward — two independent instruments (kernel-side capture of the requested
timespec64and userspace realtime-vs-monotonic differencing) agree per event to microseconds. These are tiny corrections applied as hard clock steps, every 30 s, for as long as Docker Desktop is running (observed across three days).Suggested fix
Periodic guest-clock correction is legitimate (WSL2 guests genuinely drift across Windows sleep — cf. microsoft/WSL#10006). The problem is stepping rather than slewing:
adjtimex(gradualADJ_OFFSETslewing — notADJ_SETOFFSET, which is itself a step) instead ofsettimeofday. A slew is not aCLOCK_REALTIMEdiscontinuity and does not wakeTFD_TIMER_CANCEL_ON_SETwatchers — no cache flush, no log spam. This is exactly what the systemd maintainer prescribed when the consumer half of this was reported there (resolved: on_clock_change() flushes the entire DNS cache on any CLOCK_REALTIME step, with no magnitude check systemd/systemd#43028, closed not-our-bug: "if wsl makes continious small steps then they really should use the gradual clock changes (adjtimex) instead of the stepping ones (clock_settime)").Either change stops the flush loop in every co-resident distro while keeping real post-resume corrections (a large step after actual sleep would still step, correctly).
Reproduction
Environment: Windows 10 (19045-class) host, WSL2 kernel 6.6.114.1-microsoft-standard-WSL2, Ubuntu 24.04 (systemd 255.4) co-resident with
docker-desktop; reproduced 2026-07-14 and re-confirmed 2026-07-16/17 on Docker Desktop 4.77.0.Two honest boundaries: (1) I did not disassemble
/initd, so I can't say why it re-steps every 30 s rather than on demand — though the joined trace confirms each call writes a fresh backward ~1 ms correction and succeeds — and the fix direction (slew or magnitude-gate) holds regardless. (2) This trace explains the cache-flush loop and the log spam specifically; whether flushing alone accounts for every network symptom reported upstream is for the thread to judge.Cross-references: microsoft/WSL#13777 (same symptom, correctly correlated with Docker Desktop by its reporters), systemd/systemd#43028 (the resolved-side half, closed with "report to the stepper").
0xdhx commentedon Sep 18, 2026
Re-confirmed on current Docker Desktop, and adding the intake data this report's fields were left
blank for.
Still reproducing on 4.89.0
Measured today (2026-09-17) with the same instrument as my July capture — an absolute
CLOCK_REALTIMEtimerfd armedTFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, counting cancellations,alongside an independent realtime-vs-monotonic watcher:
Unchanged from July in cadence (30.00 s), direction (backward) and magnitude (~1–2 ms), on a Docker
Desktop two minor versions newer.
Intake data
01EFED8D-7EBF-4EAA-BB3D-167A7612FBC3/20260918022054docker version:docker info(relevant lines):Mechanism (unchanged from my earlier comment)
Full trace in #761 (comment). In short: the
30-second step is a userspace
settimeofday(2)call from Docker Desktop's/initdinside thedocker-desktopdistro, attributed underbpftracetocomm=initd, withhv_set_host_time— theHyper-V TimeSync setter — at zero calls throughout the capture. Because these co-resident distros
share one utility VM and one
CLOCK_REALTIME, the step lands in every distro on the box.Because the observed calls are userspace
settimeofdayrather than the Hyper-V path,hv_utils.timesync_implicit=0does not address this particular mechanism — that parameter governsimplicit host synchronisation, not userspace callers.
Suggested fix
Unchanged: slew small deltas via
adjtimexgradualADJ_OFFSETrather than stepping (noteADJ_SETOFFSETis itself a step, so not everyadjtimexmode qualifies), or gate the hard step onmagnitude so a genuine post-resume correction still steps while a ~1 ms one does not. A slew is not a
CLOCK_REALTIMEdiscontinuity and does not cancelTFD_TIMER_CANCEL_ON_SETwatchers.For reference, the systemd maintainers closed the consumer-side report (systemd/systemd#43028) as
not-their-bug, recommending gradual adjustment over stepping. They attributed the stepping to WSL and
asked for it to be reported there; the attribution to Docker Desktop's
/initdis from my latertrace, not theirs. The magnitude-gating option is my suggestion, not part of their recommendation.
Scope
I traced the successful syscalls and their effects; I did not inspect
/initd's implementation, so Ican't say why it re-steps every 30 s rather than on demand. This explains the periodic clock steps
and the cache-flush messages they cause. It does not establish the cause of every DNS or connectivity
failure reported in the WSL thread — @mihalt reported DNS problems there that persisted after Docker
Desktop was removed.
Happy to supply a bundle, a bpftrace capture, or a test-build result.
tobiasscott-web commentedon Oct 19, 2026
Description
Docker Desktop's init process in the docker-desktop WSL2 distro appears to step the shared WSL2 VM clock backwards roughly every 30 s via settimeofday(). This looks like the same cause as the reports above (4.67.0, 4.77.0, 4.89.0); it is also reproducible on 4.95.0.
Reported observations (from a trace taken in an Ubuntu 24.04 WSL2 distro with kernel ftrace syscall tracepoints):
Reproduce
Expected behavior
Small clock corrections are slewed (adjtime/adjtimex) instead of stepped, or small offsets are skipped, so other WSL2 distros don't see clock jumps. Alternatively, an option to disable Docker Desktop's own time sync.
docker version
Docker Desktop 4.95.0 (build 242831), engine/CLI 29.8.2
(full
docker versionoutput: not provided)docker info
(not provided; redact host-specific details before posting)
Diagnostics ID
(add only if the reporter chooses to share it)
Additional Info
WSL 2.7.11.0, kernel 6.18.33.2-microsoft-standard-WSL2, Windows 11 build 10.0.26300.9711. Clock offset vs Windows stays under ~0.3 s. Ruled out: Docker Desktop restart, stopping systemd-timesyncd, wsl --shutdown, Windows reboot, fixing w32time, hv_utils.timesync_implicit=0.
hbigot1970 commentedon Oct 19, 2026
Still reproducing on Docker Desktop 4.95.0 (build 242831, engine 29.8.2), WSL 2.7.11, Windows 10.0.26300.9711, Ubuntu 24.04 distro. Also reported to Docker Support, who asked me to bring the data here.
Symptom (unchanged)
systemd-resolvedin Ubuntu logsClock change detected. Flushing caches.every 30 s (121 times in the last hour) and drops its whole DNS cache each time.journaldlogsTime jumped backwards. If you fully quit Docker Desktop, the flushes stop at once. They come back when you start it again.New data points vs. the earlier comments here
Kernel ftrace syscall tracepoints, enabled from the Ubuntu distro (all distros share one kernel, so docker-desktop processes are visible):
initdchild with a new pid, every 29.97 s, so/initdseems to fork a helper on a 30 s timer.settimeofdayis the only syscall that steps the clock (noclock_settime).chronyd(pid 111 in the init pid namespace, not visible from Ubuntu or docker-desktop) callsclock_adjtimeevery 8 s. The tick wanders between 10000 and 10190 (normal is 10000). That may be why the guest clock keeps drifting between the/initdcorrections. Resetting the tick by hand is reverted within seconds.Ruled out
Stopping systemd-timesyncd, restarting Docker Desktop,
wsl --shutdown, rebooting Windows, fixing Windows w32time (now synced to an NTP pool), andhv_utils.timesync_implicit=0on the kernel command line. None of them stopped the 30 s steps. Only quitting Docker Desktop does.Suggested fixes
adjtime/clock_adjtimewithADJ_OFFSET) instead of stepping withsettimeofday.makestep), which would cover real host sleep/resume.A Diagnostics ID gathered during the stepping has been shared with Docker Support.
Full ftrace output (settimeofday / clock_adjtime / adjtimex, 95 s)
hbigot1970 commentedon Oct 21, 2026
Still reproducing after updating to Docker Desktop 4.96.0 (build 243774, engine 29.9.1, released today), WSL 2.7.11, Windows 10.0.26300.9711, Ubuntu 24.04 distro.
State on 4.96.0 (measured 2026-10-21, ~10 minutes after the update)
systemd-resolvedstill logsClock change detected. Flushing cachesevery 30.0 s (e.g. 17:27:15.692, 17:27:45.692, 17:28:15.689, 17:28:45.684 UTC), right from Docker Desktop's restart, so the DNS cache stays useless in every WSL distro.CLOCK_REALTIME − CLOCK_MONOTONICsampled every 50 ms for 92 s: one backward step every 30 s, of −8.7 ms, −7.8 ms, −8.5 ms (smaller than the ~−30 ms I saw on 4.95.0 two days ago, but still a step rather than a slew).settimeofday: 2 calls, 29.99 s apart, both frominitd(one from a long-livedinitdprocess, one from a short-livedinitdchild; on 4.95.0 every call came from a new short-lived child);clock_adjtime: 32 calls fromchronyd(≈ every 2 s, versus every 8 s on 4.95.0) and 2 fromsystemd-timesyncd.So the update did not change the behaviour:
initdstill steps the shared clock every 30 s, whilechronyd(WSL system distro) now disciplines it more often. Both keep fighting, and everyinitdstep triggers the cache flush in all distros.Still suggested
clock_adjtimewithADJ_OFFSET) instead ofsettimeofday.chronydalready disciplines the clock.Happy to test a pre-release build.