Files
2026-10-11 12:01:16 -07:00

50 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Windows clock sync (w32time) for latency-gated work
> Reusable failure mode. Diagnosed 2026-10-11 on Tony's desktop (physical ASUS box) while building a latency-gated paper harness whose spec voids any candidate whose NTP skew exceeds 250 ms.
## Symptom
Anything that gates on the local clock fails: an NTP gate rejects every candidate, timestamps look wrong, and the clock **drifts** instead of holding. On this box the offset went from ~−1.8 s, to ~+10 ms right after a sync, then slid to **−284 ms and kept going** (~4–5 ms/min).
## Diagnose
```bash
sc.exe query w32time # STATE: 1 STOPPED == no time sync at all
w32tm /query /status # Leap Indicator, Stratum, Last Successful Sync, Poll Interval
```
Then measure the offset **independently** of w32time (a stdlib NTP query against `pool.ntp.org`, `time.google.com`, `time.cloudflare.com`). Multiple servers agreeing confirms it is a real local-clock error, not one bad source. HTTP `Date` headers are useless for sub-second work — they are **whole-second** granularity.
## Root causes seen (in order encountered)
1. **The service was stopped.** `w32time` not running ⇒ no sync at all.
2. **Registry writes went to the wrong key path.** `MinPollInterval`, `MaxPollInterval`, `MaxAllowedPhaseOffset`, `LocalClockDispersion` live under
`HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config` — **not** the service root (`…\Services\W32Time`). `reg add` reported success but nothing applied. `SpecialPollInterval`/`NtpServer` live under `…\W32Time\TimeProviders\NtpClient`.
3. **Default poll is 1024 s**, so a bad RTC drifts far between syncs.
4. **`MaxAllowedPhaseOffset = 1` (s) makes w32time *slew*, not *step*** — it never nulls a sub-second residual. The clock sat stable but stuck at ~−180 ms.
## Fix (all elevated)
```bat
set CFG=HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config
set NTP=HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient
sc config w32time start= auto
w32tm /config /manualpeerlist:"time.google.com,0x1 time.cloudflare.com,0x1 pool.ntp.org,0x1" /syncfromflags:manual /reliable:no /update
reg add "%CFG%" /v MinPollInterval /t REG_DWORD /d 6 /f REM 64 s
reg add "%CFG%" /v MaxPollInterval /t REG_DWORD /d 10 /f
reg add "%CFG%" /v LocalClockDispersion /t REG_DWORD /d 3 /f
reg add "%CFG%" /v MaxAllowedPhaseOffset /t REG_DWORD /d 0 /f REM force a STEP to null the residual
reg add "%NTP%" /v SpecialPollInterval /t REG_DWORD /d 64 /f REM honoured only with the 0x1 flag
net stop w32time & net start w32time
w32tm /resync /force
```
Result: **−284 ms drifting → ±2 ms stable** (held over 150 s across four NTP servers).
## Lessons
- A `reg add` that "completed successfully" can be a **no-op** if the key path is wrong. Verify with `w32tm /query /configuration`, never with the command's exit code.
- `MaxAllowedPhaseOffset: 0` forces a step on every sync — right for nulling a residual; if constant stepping is unwanted, raise it to a small tolerance afterwards.
- Post-step, `w32tm /query /status` can read `Leap Indicator: 3 (not synchronized)` / `Stratum 0` while the time is actually correct — trust an independent NTP measurement, not the status flag.
- `Date` headers (1 s granularity) can never satisfy a sub-second freshness/skew gate; measure freshness from **local timestamps** instead.
- Requires elevation (UAC). A physical box with a bad RTC has no host time-integration to fall back on, unlike a VM.