Repository navigation
ci: Linux/MSHV restore-processors marks restored tsc-early unstable via clocksource watchdog #253
Description
Summary
The Linux/MSHV restore-processors microVM scenario failed on a dev push at its first restore target (1 vCPU). Linux's clocksource watchdog measured tsc-early 142 ms ahead of refined-jiffies over a 510 ms interval. The guest marked the TSC unstable and switched to refined-jiffies, and the scenario's TSC guard failed the job.
This is not another #211 occurrence. #211 is a 6–45-cycle (2–20 ns) warp between CPUs that Linux detects while bringing up application processors. Here only one vCPU existed, and the skew is 142 ms on a single CPU. The most likely cause is the host-downtime TSC advance on restore, combined with a snapshot that was captured before Linux switched from tsc-early to tsc.
Current incident
- Workflow:
CI, run 36526970004, attempt 1,pushtodev - NVX head:
220b4f4fcbe0f7bd84c3996176472f8a94a9b2fc(merge of [code-quality] Reject malformed sandbox network lists #243) - OpenVMM gitlink:
e2c982abc2ce934750cefa58fdd0bafff61cc4d8 - Failed job: NVX microVM tests / Linux / MSHV, step 11 "Run NVX microVM tests on Linux"
- Runner:
azure-mshv-2(self-hosted,linux,mshv,virtual-machine) - Command:
python3 scripts/nvx.py test-microvm --backend "mshv" --output-dir "build/test-results/microvm-mshv" - Artifact:
nvx-microvm-tests-mshv, which expires on 2026-12-28. The guest log isrestore-processors-1.log; the capture log isrestore-processors-capture.log. - Guest: Linux 6.18.38 on a Xeon Platinum 8370C,
tsc: Detected 2793.437 MHz processor,Booting paravirtualized kernel on bare hardware - Guest command line:
earlycon=xe9 console=hvc0 reboot=t panic=-1 quiet loglevel=0 maxcpus=1 nr_cpus=8 tsc_early_khz=2793437 lapic_timer_hz=200000000 virtio_mmio.device=0x1000@0xd0001000:6 - Scenario:
restore-processors, target 1, the first restore after capture. The restore bound only VP 0 (vp_bind_bsp). Targets 2, 4, 8, and the untargeted restore did not run. - Impact:
Required status checkfailed because of this job andOpenVMM vmm-tests / Windows / WHP(ttrpc::test_ttrpc_microvm_linux_direct_lifecycle_and_snapshot, "portb closed before the expected marker", not analyzed here).Persist performance baselineandPublish development releasewere skipped for220b4f4.
Decisive guest log
NVX-CPU-ONLINE-OK: count=1
NVX-POST-RESTORE-OK: tier=legacy
NVX-RESTORE-PROCESSOR-OK count=1 cpu=0
...
[ 0.087022] clocksource: Switched to clocksource tsc-early
...
[ 0.234344] Run /init as init process
...
[ 1.231176] clocksource: timekeeping watchdog on CPU0: Marking clocksource 'tsc-early' as unstable because the skew is too large:
[ 1.231180] clocksource: 'refined-jiffies' wd_nsec: 510000000 wd_now: ffff8b3d wd_last: ffff8b0a mask: ffffffff
[ 1.231182] clocksource: 'tsc-early' cs_nsec: 652441091 cs_now: d3b107f6 cs_last: 670f138f mask: ffffffffffffffff
[ 1.231183] clocksource: Clocksource 'tsc-early' skewed 142441091 ns (142 ms) over watchdog 'refined-jiffies' interval of 510000000 ns (510 ms)
[ 1.231185] clocksource: 'tsc-early' is current clocksource.
[ 1.231191] tsc: Marking TSC unstable due to clocksource watchdog
[ 1.233078] TSC found unstable after boot, most likely due to broken BIOS. Use 'tsc=unstable'.
[ 1.233080] sched_clock: Marking unstable (1242810045, -9732733)<-(1238191087, -5113908)
[ 1.234079] clocksource: Switched to clocksource refined-jiffies
NVX-RESTORE-PROCESSORS-FAIL unstable-tsc
The harness then reported error: guest marker was not observed within 60s.
Observations:
- The guest registers only
refined-jiffies,tsc-early, andjiffies. It has no HPET, ACPI PM timer, or paravirtual clock, sorefined-jiffiesis the watchdog. - At 2793.437 MHz,
cs_lastcorresponds to guest time 0.579 s. That was the last watchdog check before capture, so the next check was due at about 1.079 s. - The capture process stopped the VP 1,118.3 ms after it started (
vp_stop_at_io_boundary). That is about 1.05–1.08 s of guest uptime, before the next watchdog check and before Linux's usual switch totsc. The restored log has noSwitched to clocksource tscline. - The first watchdog check after restore therefore compared
tsc-earlywith jiffies across the snapshot. Jiffies advanced 51 ticks (510 ms at HZ=100) while the TSC advanced 652.44 ms. The allowed margin is 64 ms: 32 ms fortsc-earlyplus 32 ms for jiffies. - The
unchecked MSR access errortraces for RDMSR0x1adand WRMSR0x199also appear in passing logs and are unrelated (see mshv: incorrect msr filtering on intel openvmm#3462).
Why this differs from #211
| #211 occurrences | This failure | |
|---|---|---|
| Linux check | check_tsc_sync_source while bringing up APs |
Clocksource watchdog on CPU 0 |
| Restore target | 2–8 vCPUs | 1 vCPU (VP 0 only) |
| Magnitude | 6–45 cycles (2–20 ns) between CPUs | 142 ms between the TSC and jiffies on one CPU |
| Clocksource at capture | tsc |
tsc-early |
With one vCPU, Linux never runs its cross-CPU warp test. advance_snapshot_time also returns before freezing time, because created_aps is empty. Neither #211 hypothesis explains a 142 ms offset on one CPU: MSHV AP alignment doesn't run here, and skew between runner vCPUs is measured in nanoseconds.
The two modes do occur together. The next dev push, run 36581320533 (1bc4f9de6), failed on the same runner with a #211 warp (Measured 20 cycles TSC warp between CPUs, CPU 0 → 1, target 2).
Why the head is not the cause
5edac1c7f..220b4f4changes only documentation ([code-documentation] Document benchmark scratch directory option #246),scripts/nvx_tools/sandbox_lifecycle.py, andscripts/test_nvx_tools.py([code-quality] Reject malformed sandbox network lists #243).- With the same OpenVMM pin,
e2c982a, this job passed ondevpush5edac1c7f(run 36522960728,azure-mshv-6) and on Restore listener rebinding integration #236's PR head74cfd41ac(run 36519404349,azure-mshv-5).
Capture timing
Linux switches from tsc-early to tsc when its TSC refinement work runs again, HZ jiffies after its first run. In every available MSHV restore log, the switch happened at 1.118–1.120 s of guest uptime. Every other logged MSHV capture happened after the switch, and none of those restores hit the watchdog.
| Run | Failure | Capture (vp_stop_at_io_boundary, ms of process time) |
tsc registered before capture |
|---|---|---|---|
| 36526970004 (this) | Watchdog, target 1 | 1,118.3 | No; tsc-early still current at 1.231 s |
| 36581320533 | #211 warp, target 2 | 1,278.9 | Yes, at 1.119181 s |
| 36434572767 | #211 warp, target 8 | 1,308.5 | Yes, at 1.118149 s |
| 36495641378 | #211 warp, target 8 | 1,327.0 | Yes, at 1.118682 s |
Run 36581320533 used the same OpenVMM pin and runner. The pre-capture console transcript matches #211 occurrence 4 except for the LOC interrupt counts. The early capture therefore looks like run-to-run timing variance, not a script or pin change.
Relevant control flow
At OpenVMM e2c982a, NVX 220b4f4, and Linux v6.18:
- Every microVM restore computes host downtime as the destination wall clock minus the capture wall clock (
validate_restore_contract,calculate_snapshot_downtime). advance_restored_clockadvances each VP's TSC by that downtime (TscAdvance::apply).Apic::advance_timerqueues a single interrupt for all periodic LAPIC periods that elapsed during the downtime and preserves the timer phase.supported_features1hidesTSC_ADJUSTfor versioned MSHV CPU contracts.check_system_tsc_reliabledisables the TSC watchdog only whenTSC_ADJUSTis present along with a constant, nonstop TSC, so here the watchdog stays enabled. The log confirms thattsc-earlywas watched.- A watched clocksource becomes valid for high-resolution mode only under a continuous watchdog (clocksource.c), and jiffies is not continuous (jiffies.c). While
tsc-earlyis current, the tick therefore stays periodic, and jiffies count delivered ticks.tscis registered already valid for high-resolution mode (tsc.c). Once it takes over, the kernel can use one-shot ticks, which recompute jiffies from the clocksource. - The watchdog skips intervals over 1 s and otherwise flags skew above the combined margin (clocksource.c). A newly registered clocksource is only baselined on its first check (clocksource.c).
- After capture, the harness hashes all snapshot files, including the 128 MiB
memory.bin(_snapshot_fingerprint, call site). It then starts the target-1 restore. The guard readsdmesgonce, shortly after resume.
Root-cause hypothesis
The target-1 restore advanced the guest TSC by about 150 ms of host downtime. Because tsc-early was current at capture, the guest was on periodic ticks, and the missed ticks arrived as a single interrupt. Jiffies advanced by one tick while the TSC advanced by about 150 ms. The first watchdog check after restore compared the two across the snapshot and marked the TSC unstable: 142 ms of skew is the downtime minus one 10 ms tick. That check had been due just after capture, so it fired before the guard read dmesg.
This should reproduce deterministically on MSHV when three conditions hold:
- The snapshot is captured after the watchdog's first
tsc-earlybaseline (about 0.57 s) and before the switch totsc(about 1.12 s). - The restore applies more than about 75 ms of downtime.
- The watchdog interval that spans the snapshot stays under 1 s.
In CI the failure is intermittent because capture normally happens after the switch. This capture was at least 160 ms earlier than the other MSHV captures in the table.
The CI logs don't record the downtime that was applied. _restore_environment() sets OPENVMM_LOG=off, and the adjusted restored vCPU TSC trace is at debug level. The estimate of about 150 ms is the skew plus one tick. It is consistent with the capture process exiting, the snapshot being hashed, and the restore starting.
Two consequences follow if the hypothesis holds:
- The spanning watchdog check can fire up to about 500 ms after resume, after the guard has already read
dmesg, so an early capture can mark the TSC unstable without failing the test. Whentsc-earlyis marked unstable, the refinement work never registerstsc(tsc.c), and the guest stays onrefined-jiffies. Passing runs don't rule this out. - The hazard is not specific to CI. It applies to any snapshot captured while
tsc-earlyis current and restored with more than about 75 ms of downtime, whenever the TSC watchdog uses jiffies.
Recurrence
- I scanned all 454
CIruns created between 2026-09-17 01:08 UTC and 2026-09-29 14:30 UTC. The 114 runs that failed or were re-run contain 68 failed NVX microVM jobs across all attempts: 25 MSHV, 19 KVM, and 24 WHP. Two WHP jobs had no logs. - Only this job contains
timekeeping watchdog. Five jobs contain the ci: intermittent restored-TSC warp on Linux virtual-machine runners (MSHV, KVM) #211 warp: 35948673756, 36205231279 (attempt 2, KVM), 36434572767, 36495641378, and 36581320533. - Passing jobs upload no guest logs (
if: failure()), and the guard readsdmesgonly once. Silent occurrences therefore can't be counted.
Related work
- ci: intermittent restored-TSC warp on Linux virtual-machine runners (MSHV, KVM) #211: intermittent restored-TSC warp between CPUs on the Linux VM runners. It shares the guard and runner with this failure, but not the mechanism.
- MSHV restores ignore the --restore-processors VP prefix (restore_vp_count is never read) #219 and nanvix/openvmm@5cfdf92: MSHV restores now bind only the requested VP prefix, which is why target 1 bound only VP 0.
- Fix KVM TSC advancement across snapshot downtime in OpenVMM #27 fixed KVM TSC advancement across snapshot downtime, and Update OpenVMM to validate time-adjusted restores #136 validated time-adjusted restores.
- Define portable CPU and clock snapshot contracts openvmm#4437: define portable CPU and clock snapshot contracts.
Suggested next steps
- Reproduce this on a Linux/MSHV
virtual-machinehost. Capture arestore-processorssnapshot between about 0.6 s and 1.1 s of guest uptime, restore it with 100–400 ms of downtime, and readdmesgmore than 0.5 s after resume. Repeat with a capture afterSwitched to clocksource tsc. Collect OpenVMM debug logs that includeadjusted restored vCPU TSC. - Record the applied restore downtime and the capture-time clocksource in the restore scenarios' CI logs.
- Strengthen the guard so that it also catches a spanning watchdog check that fires after its first
dmesgread. For example, checkdmesgandcurrent_clocksourceagain once a full watchdog interval has passed after resume. - Decide the restore clock contract for guests captured while
tsc-earlyis current. Options include capturing only aftertscis current, delivering the missed periodic ticks instead of a single one, or giving the guest a continuous clocksource the watchdog can use. Don't weaken the guard, add retries, or force a fallback clocksource,tsc=reliable, ortsc=nowatchdog.
Activity
ci: record restored TSC downtime in restore logs
ppenna commentedon Sep 29, 2026
Reproduced on Linux/MSHV; #251 avoids it in CI
I reproduced this deterministically on
azure-mshv-7(vmss000006, Xeon Platinum 8573C) and on a bare-metal Linux/MSHV lab host (2× Xeon Silver 4114), following the first suggested step. #251 now makes MSHV captures wait untiltscreplacestsc-early, as WHP captures already did.Method
I used a test-only variant of the
restore-processorscapture:hvc0busy, so the guest reads the host's snapshot trigger promptly.nvx-snapshot.maxcpus=1. The untargeted restore starts 0.15 s after the capture process exits.adjusted restored vCPU TSCdebug event reports the applied downtime.dmesgandcurrent_clocksource.Results
tsc-earlyazure-mshv-7tsc-earlyazure-mshv-7tsc-earlyazure-mshv-7tsc-earlyazure-mshv-7tsc-earlytsc-earlyazure-mshv-7tscThe failures logged the same messages as run 36526970004. For example, a 1.00 s capture on
azure-mshv-7logged:What the results add to the hypothesis
tscabout 1.12 s after boot. A capture at 1.10 s, still ontsc-early, passed. The first successful comparison makestsc-earlyvalid for high-resolution mode, and the one-shot tick then recovers the jiffies after the restore. Run 36526970004 captured just before that comparison, which ran atwd_now= 109 jiffies, right after the restore.restore-processorsrun onazure-mshv-7, the applied downtime grew from 124 ms at target 1 to 306 ms at target 2 and 686 ms at target 4, because every target restores the same snapshot. From about target 4 onward, the spanning watchdog interval likely exceeds 1 s, so the check is skipped. Targets 1 and 2 remain exposed.What #251 changes
tsc-early. This is the first contract option, applied to the harness. It coversrestore-processors,restore-tsc-sync,smp-snapshot, and MSHV benchmark captures.adjusted restored vCPU TSCevent with the applied downtime, as the second step asks.restore-processors,restore-tsc-sync, andsmp-snapshotpassed 30 of 30 runs on each host. Fulltest-microvmruns passed 3 of 3 onazure-mshv-7and 2 of 2 on the lab host.#251 doesn't strengthen the guard (the third step) or decide the restore clock contract for snapshots taken outside the harness (the fourth step). A snapshot captured in this window by other tooling still loses the TSC clocksource after restore.
ppenna commentedon Sep 30, 2026
Design suggestions
#251 keeps CI captures out of the
tsc-earlywindow. The guard (third step) and the restore clock contract for other capture paths (fourth step) are still open. This comment explains why the guest stays ontsc-earlyfor a second, then proposes a contract and guard options and lists the alternatives I considered. I prototyped the recommendation and validated it on bare-metal Linux/MSHV, Linux/KVM, and Windows/WHP hosts and onazure-mshv-7(vmss000006). The results are at the end; the prototype isn't submitted.Why MSHV and WHP guests stay on
tsc-earlytsc_early_khz=<backend TSC kHz>to every microVM boot that can publish a snapshot (prepare_cold_boot_command_line), so the guest uses the exact frequency that the machine contract records.x86_platform.calibrate_tsc()(tsc.c). On Intel, that callback reads CPUID leaf 0x15, which OpenVMM fills in, and marks the frequency as known (tsc.c). The parameter that pins the frequency exactly therefore also keeps Linux from treating it as known.init_tsc_clocksource()starts a refinement instead of registeringtsc(tsc.c). The refinement registerstscone second later. It compares against an HPET or ACPI PM timer, and the microVM has neither, so it registerstscwith the frequency unchanged (tsc.c). The second ontsc-earlygains nothing.calibrate_cpucallback runs even withtsc_early_khzand marks the frequency as known (kvmclock.c, L323-L324), sotscis registered at boot. The watchdog's reference on KVM is kvm-clock, which restore advances.Why the 1.10 s capture passed
The comment above attributes this to the first successful comparison making
tsc-earlyvalid for high-resolution mode. That can't happen here: the watchdog setsCLOCK_SOURCE_VALID_FOR_HRESonly when its reference is continuous (clocksource.c), andrefined-jiffiesisn't (jiffies.c). The tick stays periodic untiltscis registered.The 1.10 s capture passed because the refinement, due at about 1.12 s of guest time, ran after the restore and before the watchdog's next comparison. It removed
tsc-earlyfrom the watchdog, the one-shot tick caught jiffies up (tick-sched.c), andtscstarted from a fresh baseline. The failing window is unchanged: from the watchdog's first reading oftsc-early(about 0.58 s) to its first comparison (about 1.08 s).Suggested restore clock contract
Capture only when the current clocksource's watchdog comparison survives a restore:
tscor kvm-clock, with a one-shot tick. Enforce this in the NVX kernel so it holds for every capture path, not only the harness. The recommended fix is a new NVX kernel patch that treatstsc_early_khzas a known frequency:init_tsc_clocksource(), adevice_initcall, then registerstscat about 0.09 s instead of 1.12 s. Captures are requested from guest userspace, which starts later, so no capture path can seetsc-early.tsc=reliable,tsc=nowatchdog, fallback clocksource, or retry. Boots withouttsc_early_khzare unchanged.tsc-earlywithout waiting.tsc-earlycan't be repaired and must be recaptured.One case remains for every option that relies on
tsc. A guest booted withnohz=off highres=offkeeps a periodic tick even ontsc, and would hit the same skew after any restore with roughly 64–500 ms of downtime. A capture-time check of the tick mode catches it (see the guard options).Alternatives considered
tsc(#251, current)nvx-snapshotwaits for, or refuses,tsc-earlynvx-snapshot, on any kernelscratch-paired.shand OpenVMM's microVM tests (ttrpc, x86_64). Without the kernel fix, it delays every capture before about 1.12 s, including safe ones before the watchdog's first reading, and it must fail rather than capture on timeouttsc_early_khzand relies on CPUID leaf 0x15Not recommended, because they turn off the watchdog or bypass the restore contract:
tsc=reliableortsc=nowatchdog; exposingTSC_ADJUST(Linux then turns off the watchdog, and a snapshot can't preserve the register separately fromIA32_TSC); resetting the watchdog after restore (it would hide a TSC that didn't follow the restore); and not advancing the TSC over the downtime.nvx-snapshotrefusingtsc-earlywithout waiting is still worth adding alongside the kernel fix. It never triggers when the fix is present, and it turns a kernel that lacks the fix into a capture failure instead of a snapshot that breaks on restore.Guard options (third step)
The guard reads
dmesgonce, right after resume (restore-processors.sh), but the comparison that spans the snapshot can run up to 0.5 s later.dmesgagain after a fixed 1 s, and fail ifcurrent_clocksourcechanged (prototyped)azure-mshv-7went from about 0.3 s to 1.3 s)jiffiesbefore capture; after restore, wait until it has advanced by 51, then readdmesgazure-mshv-7, about 0 on the bare-metal MSHV host and on WHPjiffiesandnow atfrom/proc/timer_listbefore capture; after restore, fail if jiffies trailCLOCK_MONOTONICby more than the watchdog's 64 ms margincurrent_clocksourceand require a one-shot tick (Tick Device: mode: 1in/proc/timer_list)I suggest the
/proc/timer_listcomparison plus the capture-time check. Logging the clocksource at capture also completes the second step. Add the jiffies-deadline re-read if the kernel's own verdict is wanted.snapshot-coreshould get the same capture-time check on MSHV, where it has none today (microvm_tests.py).Prototype results
The prototype had the kernel patch above, capture checks that don't wait, and the fixed 1 s guard re-read. It used the
v0.1.0-dev.1cb930f45360OpenVMM binaries and a kernel built with CI's Docker toolchain, with the release kernel as the baseline.A test-only reproduction booted 8 VPs with
maxcpus=1, captured at a fixed uptime, and ran an untargeted restore 0.15 s after the capture process exited. It then readdmesg1.5 s after resume. I ran five captures per capture time.azure-mshv-7(vmss000006)tscalready currenttsc-early: the downtime was too long for the watchdog to comparetscreplacedtsc-earlyat 0.09 s,/proc/cpuinfolistedtsc_known_freq, and the shell was ready at the same uptime as before (about 0.40 s on MSHV and 0.36 s on KVM).restore-processors,restore-tsc-sync,smp-snapshot, andsnapshot-corepassed 10 of 10 on both MSHV hosts and 5 of 5 on KVM and WHP. Fulltest-microvmpassed 2 of 2 on each host. The Ubuntu smoke tests passed on all four, and the extended Ubuntu tests passed on KVM. Every logged capture and every restore-processor restore was ontsc, and no TSC-instability messages appeared.restore-processorsin 2.3 s withSMP-CLOCKSOURCE-FAIL expected=stable actual=tsc-early.ppenna commentedon Sep 30, 2026
Implementation design: capture only tickless guests
This design implements the restore clock contract (fourth step) and replaces the kernel patch recommended in the design suggestions. It follows these constraints:
It hasn't been prototyped yet.
Why the fix belongs at capture
Apic::advance_timerqueues a single interrupt for all the periods that a periodic LAPIC timer missed. A guest on a periodic tick adds one jiffy per interrupt, so its jiffies fall behind its TSC by the downtime. Whiletsc-earlyis current, the watchdog compares exactly those two.tsc-earlynever becomes valid, because its only watchdog there isrefined-jiffies.Contract
A microVM snapshot may be captured only when:
tscorkvm-clock; andThe second condition is the one that matters. The first rejects a guest that has already fallen back to
refined-jiffies. The clocksource name alone, which #251 checks, isn't enough: withnohz=off highres=off,tsckeeps a periodic tick, and aftertscregisters, each CPU stays periodic until its next tick.NVX guest:
nvx-snapshotwaits for the contractThis is where the capture delay goes. Every NVX capture path already runs
nvx-snapshot, exceptscratch-paired.sh(see below).After the existing pre-capture checks and before any freeze (L444), poll every 10 ms, for up to 5 s, until the contract holds:
/proc/timer_listis always built (Makefile) and is readable by root, whichnvx-snapshotalready needs for/dev/port. It lists only online CPUs. Its first entry is the broadcast device (timer_list.c), which the parser skips. The sketch passes against sample output in that format: all CPUs one-shot, one CPU still periodic,tsc-early,kvm-clock,refined-jiffies, and no per-CPU entries.Log
NVX-SNAPSHOT-CLOCK: source=<name> waited_us=<n>to the console, so every log records the capture-time clocksource (second step).On timeout, exit 1 without requesting a capture, like the other pre-capture checks. The VM keeps running.
The guest has to do the waiting, not OpenVMM. The requesting vCPU stays blocked in its
outuntil OpenVMM completes the deferred write, so a guest with one online CPU couldn't reach a one-shot tick while OpenVMM held the write.Expected cost: on MSHV and WHP, a capture requested before Linux registers
tsc(about 1.12 s after boot) waits until then, up to about 0.7 s after the shell is ready. KVM guests registertscat boot, so KVM captures and all later captures pay only for the check.NVX harness, tests, and documentation
stable_clocksource_wait_scriptand its uses in the capture controller and in WHPsnapshot-core. The controller already runsnvx-snapshot. MSHVsnapshot-core, which has no check today, gains one.scratch-paired.shrequests capture by writing to port0x605directly. Make it run the same check.clock_readywith/proc/timer_listfixtures: broadcast entry first, mixed per-CPU modes, all one-shot,kvm-clock, andrefined-jiffies.OpenVMM: refuse what the VMM can see
Apic::periodic_timer_armed()next toadvance_timer. It's true when the LVT timer is unmasked, in periodic mode, and has a nonzero initial count, which is exactly the state thatadvance_timercoalesces.SnapshotRequest::PeriodicLapicTimers, modeled onAdvanceTsc. It sends a per-VP state RPC that readsapic()through the same state access asTscAdvance::apply, and returns the VPs that have an armed periodic timer. At the capture boundary the VPs are already stopped (stop_at_io_boundaryclearsstarted), so the query needs no further quiesce.quiesce_for_snapshot, beforequiesce_for_save, returnSnapshotQuiesceError::Rejectedif the query returns any VP. The controller already releases the boundary and lets the guest continue.nvx-snapshotexits 0 after a rejected capture, the same as after a restore without a restore packet, and reads of0x605return0xff.io_read, asio_writealready does, and return0for no rejection,1for a periodic LAPIC timer, and2for any other rejection. A restored VM starts at0.nvx-snapshotreads the port after its write returns. On a nonzero value it thaws its barriers and exits 1. It treats0xffas a VMM without the status.0x605directly.TscAdvance::applyfail a restore whose saved LAPIC timer is armed and periodic, with a message to recapture, instead of coalescing it. This adds no waiting, but it rejects existing MSHV snapshots taken duringtsc-early, which can't be repaired anyway.What doesn't change
Restore and its latency,
tsc_early_khz, the TSC advance, the clocksource watchdog, and the NVX kernel all stay as they are. Nothing is vendor-specific. The guard (third step) keeps its singledmesgread. A snapshot that meets the contract can't produce a watchdog comparison oftsc-earlythat spans the snapshot, and a post-restore wait would add restore latency.Delivery
nvx-snapshotcheck, the harness cleanup,scratch-paired.sh, tests, and documentation. This doesn't depend on OpenVMM, and it protects every NVX capture path immediately.0x605status, WHP TSC-deadline hiding, the test updates, and the guide. Then promote it into NVX with a gitlink update.nvx-snapshothandles the0x605status, and the ABI documentation changes.Validation
azure-mshv-7, and a Windows/WHP host with short downtime. Expect each capture to wait until the guest is tickless, no restore to mark the TSC unstable, andwaited_usto appear in the logs.nohz=off highres=offand expectnvx-snapshotto refuse after 5 s. Write to0x605directly duringtsc-earlyand expect OpenVMM to reject the capture on MSHV and WHP, the guest to continue, and0x605to read1.restore-processors,restore-tsc-sync,smp-snapshot, andsnapshot-core10 times, and fulltest-microvmtwice, on each backend. Include at least one AMD host; none of the earlier runs used one.periodic_timer_armed, the VP query, the rejection inquiesce_for_snapshot, and the0x605status, including in a restored VM.Alternatives under these constraints
tsc_early_khzas a known frequency (previous recommendation)nohz=off highres=off. It could later remove the capture wait on top of this designsetcpuid=tsc_known_freqtsc_early_khzand rely on CPUID leaf 0x15kvm_get_tsc_khzmarks the frequency known. But it's a new paravirtual interface, the MSHV backend installs no MSR intercepts, guests would identify as KVM, and the watchdog would compare the TSC with a clock derived from it. It's the way to remove the capture wait later without a kernel patchTSC_ADJUST, usetsc=reliableortsc=nowatchdog, or reset the watchdog after restoreCONFIG_SUSPENDis off, and monotonic time would stop counting the downtimeout, so a guest with one online CPU can't make progressmicrovm: enforce the snapshot clock contract
virt_whp: retain TSC-deadline for microVM snapshots
microvm: preserve WHP TSC-deadline snapshot boots
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
ci: stop waiting for a stable clocksource before snapshot capture
doc: specify NVX time ABI v1
ci: stop waiting for a stable clocksource before snapshot capture