ci: track azure-mshv-1's intermittent H4 failures after a live migration to a host with an unstable clock
I maintainer di solito rispondono entro 1 giorno
@ppenna ci sta già lavorando.
Dal 22/10/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Summary
Since 2026-10-21, runner validation has failed three times on azure-mshv-1 (machine esaurez-nvx-ci-linux-vmss000000), each time in H4 alone, the host TSC rate check of nvx.py doctor. No test ran in any of the three jobs. In the 400 CI runs from 2026-10-20 03:49Z to 2026-10-22 17:31Z, runner validation ran 3,548 times and failed only twice, both times on this runner; the third failure followed at 18:47Z.
By every sign visible inside the VM, Azure live-migrated azure-mshv-1 to another physical host on 2026-10-18 at 19:35:41Z. Measured from inside the VM on 2026-10-22, the host clock that Hyper-V reports drifts against the TSC by tens of ppm within minutes, while on azure-mshv-4 it stays within a few ppm. chrony disciplines the VM's clock to that host clock, and H4's 2-second sample measures chrony's momentary correction rather than the TSC. H4 fails when the host's drift and chrony's catch-up push the reading past 100 ppm. The TSC itself is stable, and the product isn't at fault, although H4's design makes it sensitive to this (see Next steps).
The investigation also found an OpenVMM VM that a cancelled job had left running on this runner for 8 days. It didn't cause the H4 failures, but the test harness lets such leaks happen (see below).
Incidents
| Run (attempt) | Commit | Job | H4 reading | Outcome |
|---|---|---|---|---|
| 39688204465 (1) | 823aff0 | MSHV / Debug kernel, 2026-10-21 23:44Z | +109.2 ppm | Not rerun |
| 39821559035 (1) | 2f4b337 | MSHV / Debug kernel, 2026-10-22 17:22Z | +100.03 ppm | Attempt 2 passed on azure-mshv-5 (+12.2 ppm) |
| 39833953233 (1) | 58a4a8f | MSHV / Shard 1 of 2, 2026-10-22 18:47Z | +946.1 ppm | Attempt 2 passed on azure-mshv-5 |
All three are dev pushes. The latest failure's H4 line:
NVX-DOCTOR: check=H4 status=fail detail="the backend rate is 946.1 ppm from the measured rate (limit 100 ppm); tsc_hz=2302176486 against CLOCK_MONOTONIC from 3 samples 1 s apart; against CLOCK_MONOTONIC_RAW (resolution 1 ns) the interval rates agree within 0.000 ppm, largest interval uncertainty 0.056 ppm; 946.1 ppm from the backend's 2300000507 Hz; host clocksource tsc (evidence)"
- Only H4 failed each time; H1 to H3 passed. One minute after the +946.1 ppm failure, the same run's Shard 2 of 2 passed H4 on
azure-mshv-1at +73.9 ppm. - Each failure fails
Required status check, which skips the development release and the performance-history update for that commit. Run 39688204465 was never rerun, so 823aff0 has neither. - Both reruns passed only because the scheduler placed them on
azure-mshv-5. No label distinguishesazure-mshv-1fromazure-mshv-2,-3, and-5: they all carryself-hosted,Linux,X64,virtual-machine,mshv, andgeneral, so any general MSHV job can land on it. - H4's code is identical at 764ba59, 2f4b337, and 58a4a8f, and 764ba59 passed H4 on
azure-mshv-120 minutes before the second failure. - The failure message rounds to one decimal place: the second failure was 100.027 ppm but reads
100.0 ppm from the measured rate (limit 100 ppm).
H4 readings from runner validation, 2026-10-20 to 2026-10-22, before the +946.1 ppm failure:
| Runner | Readings | Median | p95 | Max | Std dev | Failures |
|---|---|---|---|---|---|---|
azure-mshv-1 |
79 | +44.3 | +66.1 | +109.2 | 16.1 | 2 |
azure-mshv-4 (benchmark) |
255 | +52.3 | +55.5 | +84.3 | 4.2 | 0 |
In the runs of the last two failures, the KVM runners read between +0.7 and +4.3 ppm.
Root cause
The evidence comes from the CI logs and from read-only measurements on the runners on 2026-10-22, between 18:51Z and 20:19Z.
The live migration
azure-mshv-1 has been up since 2026-10-12 12:45Z. On 2026-10-18 at 19:35:41Z, without a reboot:
- Its accelerated-networking VF moved from slot 0 to slot 1, and the Hyper-V Heartbeat, TimeSync, and KVP integration services renegotiated their versions at 19:35:42.997Z.
- The Azure Linux agent got
410 Gonefrom the WireServer and restarted at goal-state incarnation 1. - Hyper-V rewrote the KVP pool that holds the physical host's name at 19:35:43Z.
None of the other nine Linux runners has had such an event since it booted on 2026-10-12. H4's baseline on azure-mshv-1 also moved, from about +46 ppm to about +44 ppm, consistent with a different host:
| Period | azure-mshv-1 H4 readings |
|---|---|
| 2026-10-12 to the event | Mostly about +46 ppm, range +27 to +63 |
| 2026-10-19 | Steady, +43.5 to +45.8 |
| From about 2026-10-20 | +7 to +109, then +946.1 on 2026-10-22 |
The new host's clock drifts
The measurements on azure-mshv-1, and for comparison on azure-mshv-4, ran while both runners were idle between jobs:
-
The host clock drifts against the TSC. The Hyper-V PTP clock (
/dev/ptp_hyperv) takes the host's time every 5 s. Its updates show the TSC's rate against the host clock, per minute:Minute (UTC) azure-mshv-1azure-mshv-420:13 +66.5 ppm +52.4 ppm 20:14 +60.8 +54.1 20:15 +51.0 +50.6 20:16 +48.1 +52.3 20:17 +44.4 +52.1 20:18 +43.3 +54.2 Earlier,
azure-mshv-1read +48.1 ppm around 20:00 and +34.8 ppm on average from 20:02 to 20:11, whileazure-mshv-4read +51.8 and +52.0. From inside the VM, it's impossible to tell whether the host's time service or its oscillator moves; the smooth decay suggests the host's time service correcting its clock. -
chrony follows the host clock. chrony's only source is that PTP clock (
refclock PHC /dev/ptp_hyperv). Onazure-mshv-1,chronyc trackingreported a frequency between 32.0 and 78.6 ppm and an RMS offset of up to 515 µs; onazure-mshv-4, 51.6 to 53.7 ppm and at most 38 µs. -
H4 reads chrony's correction. With the
tscclocksource,CLOCK_MONOTONIC_RAWis the TSC times a fixed factor, andCLOCK_MONOTONICadds chrony's frequency correction. So H4's 2-second rate againstCLOCK_MONOTONICis chrony's momentary correction, frequency estimate and offset slew together: over 75 s onazure-mshv-1, the two agreed to within 0.15 ppm on average and 2.8 ppm at worst. H4's stability check againstCLOCK_MONOTONIC_RAWcompares the TSC with a clock derived from the TSC, so on such runners it passes by construction. At +946.1 ppm, chrony was slewing the clock by about 900 ppm beyond its usual correction, which removes about 1.8 ms in H4's 2-second window.
Ruled out
- The TSC. Between host time updates, the PTP clock advances with Hyper-V's reference counter. On
azure-mshv-1,-3,-4, and-5, that counter and the TSC differ by 0.06 to 0.38 ppm, which matches each runner's truncation of its declared rate to whole kHz (the kernel'stsc_khz) to within 0.04 ppm. - Steps of the wall clock. None in 9 minutes of monitoring on
azure-mshv-1. - Jitter of individual host time updates (about ±115 µs around their mean):
azure-mshv-4shows the same pattern, more often. - The workload. No test ran in the failed jobs, and
azure-mshv-3and-5run the same jobs and stay steady.azure-mshv-1read a steady +46 ppm for days while the leaked VM below was running, and the drift continued after it was terminated. - Code changes. See the incidents above.
Leaked VM on azure-mshv-1
- An
openvmmprocess, pid 1409231, ran onazure-mshv-1from 2026-10-14 17:12:43Z at about 100% of one CPU until it was terminated on 2026-10-22 at 19:45Z. It was a 1-vCPU, 128 MiB VM with--snapshot-destination /tmp/nvx-ipv6-egress-pfqki082/snapshot, the capture VM of theipv6-egress-policyscenario. - It came from job 115990822234, the only job on the runner at the time, which was cancelled at 17:12:43Z. The runner's orphan cleanup terminated only the harness (
Terminate orphan process: pid (1407583) (python3)), and the VM, started in the same second, kept running under init. - Nothing ties OpenVMM's lifetime to the harness.
OpenvmmProcessstarts it throughInteractiveProcessas a plain child, withoutcontain_process_treeor a parent-death signal, so if the harness dies before itswithblock closes the VM, the VM keeps running. - A scan of all 20 Azure runners on 2026-10-22 at 19:46Z found no other leaked
openvmmprocess. The VM's 64 MB scratch directory,/tmp/nvx-ipv6-egress-pfqki082, is still onazure-mshv-1.
Next steps
Runner infrastructure:
- Remove
azure-mshv-1'sgenerallabel until its VM is moved, as #265 did forazure-mshv-2. - Confirm the 2026-10-18 19:35Z event in the VM's Azure activity log or Resource Health, and report the host to Azure. The physical host's name is in the VM's Hyper-V KVP data (
/var/lib/hyperv/.kvp_pool_3, keyPhysicalHostName); it's left out here because the repository is public. - Redeploy VMSS instance
esaurez-nvx-ci-linux-vmss000000so that it lands on another host, restore its label, and check that its H4 readings settle as they did before 2026-10-20. Then check whether #383's snapshot-generation rejections recur on it. - Delete
/tmp/nvx-ipv6-egress-pfqki082onazure-mshv-1, unless the redeploy replaces its disk. - Terminate the leaked VM and scan the other runners for leaked VMs (2026-10-22; none found).
H4 (scripts/nvx_tools/doctor.py):
- Print the rate deviation precisely enough that a failure can't read
100.0 ppm ... (limit 100 ppm). - Decide how H4 should treat a host clock that is still settling. Options: re-measure, or report the result as inconclusive, while chrony's offset or skew is high; compare with chrony's long-term frequency estimate instead of a 2-second window; or revisit the 100 ppm limit against the time ABI's 250 ppm restore tolerance. On
azure-mshv-1andazure-mshv-4, the declared rate already sits 44 to 52 ppm from the measured one, which leaves about half of the limit for noise. - Consider recording a host-change signal in runner validation, such as a digest of the KVP
PhysicalHostName, so that a live migration shows up next to H4's result. #526's benchmark host record holds the runner and machine names, the CPU, the OS, the VM size, and the disks, none of which a live migration changes.
Test harness (scripts/nvx_tools/openvmm_process.py and scripts/nvx_tools/benchmark.py):
- Tie each OpenVMM process's lifetime to the harness, for example with a parent-death signal, a process group that cleanup kills, or the
ProcessTreeContainmenthelper inscripts/nvx_tools/adversarial_oracles.py. - Find out why the runner's orphan cleanup, which terminated the harness, missed the VM.
- Have
validate-runnerreport, or terminate,openvmmprocesses that an earlier job left behind, so that a leak can't run unnoticed for days.
Related issues
Open:
- #383: every Linux/MSHV snapshot-generation stability rejection from 2026-09-29 to 2026-10-05 ran on this same VM. They predate the 2026-10-18 migration, so they don't share this mechanism, but they're a second kind of host-level noise confined to this VM.
- #345: transient Linux/MSHV host stalls that fail the performance gate; several of the outliers it lists ran on
azure-mshv-1. - #720: notes another way that
OpenvmmProcesscould leak OpenVMM: it buildsTimeAbiMonitoroutside its cleanuptry, although that can't fail on a valid command.
Closed:
- #507: an earlier H4 defect, in which an unreadable clocksource file failed H4 without measuring.
- #366: Linux platform jobs failing on unstable snapshot generation, where the rejections on
azure-mshv-1first appeared; #383 took over tracking the runner. - #211 and #265: TSC warps on the MSHV runners, traced to an Azure host without an invariant TSC. #265 is the precedent for removing a runner's label and redeploying it. #211 asked whether a host event such as a live migration preceded one of its incidents; the signals used here, the KVP pool's modification time and the kernel's Hyper-V integration-service messages, answer that from inside a VM.
- #526: benchmark host-change detection, which a live migration bypasses.
- Lingua principale
- Python
- Stelle
- 408
- Fork
- 28
- Merge medio
- 16h 18m
- PR unite (30g)
- 332
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di microsoft/nvx
-
Track guest managed agent bugs: output limit forwards later output after a dropped chunkForse già presa @akhil9tiet l’ha presa 1 giorno fa. Apertabug-hunt confirmed good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
microsoft/nvx#737 · 1 commento · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Track nvx-rs host VMM supervisor bugs: a non-UTF-8 process name at a reused PID blocks reconcile, reattach, and stopForse già presa @56steve l’ha presa 1 giorno fa. Apertabug-hunt confirmed good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
microsoft/nvx#720 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 20/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di microsoft/nvx
Issue simili
-
style: article/section 📝 system: cloud.rc ☁️ system: security 🔐
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
uabrc/uabrc.github.io#1262 ·
-
documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
NCAR/miles-credit#526 ·
-
documentation
Difficoltà 1/5 1-3 ore Idoneità per principianti 76/100
conda/conda-lock#957 ·
I maintainer di solito rispondono entro 1 giorno
-
Type: Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
aws-samples/sample-per-model-bedrock#464 ·
I maintainer di solito rispondono entro 1 giorno