Repository navigation
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8a79efe. Configure here.
8a79efe to
10c8f44
Compare
10c8f44 to
0bc847d
Compare
0bc847d to
bcd99d3
Compare
bcd99d3 to
0aedf45
Compare
2e09087 to
28fb9ac
Compare
28fb9ac to
17c3d4d
Compare
17c3d4d to
53b1a79
Compare
53b1a79 to
fa7578a
Compare
fa7578a to
bb25ff9
Compare
0143542 to
d58bc88
Compare
fe12f31 to
f19d7d9
Compare
f19d7d9 to
f482aaa
Compare
f482aaa to
9d2f21a
Compare
9d2f21a to
ef1d3e2
Compare
1865c45 to
da3595f
Compare
da3595f to
8229622
Compare
8229622 to
e2e2ec4
Compare
…e, and stopped clones Stopped snapshots after a graceful stop, same-instance restore through a staged disk swap, experimental same-instance standby, and clones from a stopped source with a fresh machine identifier, MAC and SSH host keys.
- Grant the macOS stop receipt only from the shutdown attempt that saw the VMM exit. Drop the separate pre-stop liveness probe, and do not count a VMM that was already gone as a graceful stop. - Clear the receipt right before the VMM starts instead of at admission, so a start that fails earlier keeps it. - Clear the source's restart block when a stopped snapshot restores to Running. - Reject explicit compression on macOS standby instead of silently ignoring it, matching snapshot create. - Stop re-saving restored metadata after the macOS directory swap, which already committed it. - Drop the unused operation argument from admitGuestOperation.
Restate the snapshot guard as a single save call so the Linux restore branch keeps its original indentation.
hiroTamada
left a comment
There was a problem hiding this comment.
reviewed at 2703576 — three recovery gaps worth addressing:
P1 — don't consume a checkpoint while its shim is still loading
lib/instances/manager.go:649–656—StateInitializingalso covers a surviving shim that hasn't opened its control socket yet (macos.go:377–390), not just a running VM waiting for its agent. retryingRestoreInstancein that interval callsfinishMacOSResume, removes the checkpoint, and returns success before loading/resumption has completed. if loading subsequently fails, the local checkpoint is gone. confirm the live VM has resumed before consuming it; keep the checkpoint and return an in-progress/conflict response for a pre-listener shim. reproduced with the process-lookup test seam: restore returned nil and deleted the checkpoint while the simulated shim was still pre-listener.
P2 — recover the snapshot source after a late standby failure
lib/instances/snapshot.go:108–111,lib/instances/standby.go:269–272— standby can save the checkpoint and shut down the VMM, then fail writing metadata. capture immediately returns that error before settingrestoreSource, leaving the previously running guest in standby. arm source recovery once the transition has happened, including late failures insidestandbyInstance. reproduced by denying metadata writes after mocked shutdown: capture failed and the source stayed in Standby. the shared Linux orchestration already has this gap; this PR newly exposes it for macOS.
P2 — finish pending clone rekeys on start retries
lib/instances/start.go:234–236,lib/instances/manager.go:737–743— if the API exits after clone boot but before rekey completes, the VMM survives withIdentityRekeyPending=true. a start retry returns early for Running/Initializing without reaching the rekey hook, so the clone can keep its source's SSH host keys while start reports success. handle pending rekeys before that fast path returns, retaining teardown-on-failure behavior. reproduced for both Running and Initializing: start returned nil with the pending flag unchanged.
validation: focused existing macOS unit tests passed; additional mocked regression tests reproduced all three findings. tests ran in an isolated source copy with compile-only placeholders for generated embedded binaries. no native VZ execution, identifier generation, or real guest SSH rekey was tested.
- A restore retried while the shim is still loading the VM returns 409 and keeps the checkpoint. The checkpoint is consumed only once the VM answers on its control socket. - Start finishes a pending clone rekey before it returns for a Running or Initializing clone, so a start retried after an API exit cannot report success with the source's SSH host keys still in place. - A snapshot whose standby fails after the VMM stopped restores the source before the error is returned, instead of leaving it in Standby. This applies to Linux as well. - Correct the docs: a shim still loading reads Initializing, and start returns the current instance rather than 409.
A standby whose final metadata save fails has already stopped the VMM, but the disk copy still predates standby. Recovery restored the source from that stale copy: a Firecracker restore could reuse the previous cycle's cache key, and the standby phase was never recorded, so Running time kept accruing across the failed standby. - standbyInstance returns the record it reached when the save fails after the VMM has stopped. - recoverSourceAfterFailedStandby saves that record before restoring the source. If the save fails, the error reports that the source is left in Standby. - Apply the same recovery to the public standby request and to fork from a running source, which had the same late-failure gap.
Tracker drops sub-millisecond phase time, so a standby record stamped at time.Now() can accrue no standby time when the restore runs in the same millisecond. CI caught this. Backdate the standby start so the assertion holds deterministically.
|
@hiroTamada thanks for the review. All three findings are addressed in
Still not covered: an end-to-end run that injects a real metadata-write fault. That is unit-level coverage only. Hardware validation of this path is pending. |
sjmiller609
left a comment
There was a problem hiding this comment.
Primary finding
This PR is introducing a new directory restore-staging like this:
<data-dir>/
├── guests/
│ └── <instance-id>/
...
│
└── restore-staging/ # NEW external temporary storage
└── <instance-id>/
├── staged/ # incoming snapshot + metadata
└── previous/ # old guest directory
but I think that any guest-specific data should always be inside of the guest directory, so that a deletion of the guest dir always results in full wipe of any guest specific data.
storage ownership/deletion: keep a stable guest directory with active storage and restore staging inside it; leftover external backups can resurrect deleted instances.
e.g.:
guests/<instance-id>/
...
└── restore-staging/
├── staged/
└── previous/
Other, less important
stale memory restore: invalidate abandoned checkpoints before resuming; otherwise a later crash can expose old memory for restore against newer disks.
graceful-stop verification: require confirmed clean shutdown before allowing stopped snapshots/clones; VMM exit alone currently counts, even after failed shutdown requests.
…e resume, and confirm guest power-off Restore staging moves under guests/<id>/restore-staging and the swap moves files within the instance directory, so deleting an instance always deletes its staging and recovery can never recreate a deleted instance. A loaded macOS checkpoint is retired before the guest resumes, so a crash after resume cannot leave it to be restored onto a newer disk. The vz-shim writes a stop receipt only when the guest stops the VM itself. A macOS stop is graceful only with that receipt; VMM exit alone no longer counts.
|
@sjmiller609 thanks, all three addressed in
Tests for each; the checkpoint and receipt tests fail on the old code. Not yet run natively on a Mac. |

Summary
PR 4 of 6. Adds macOS stopped snapshots, same-instance memory snapshots and restore, snapshot schedules, and stopped clones. Base is #500, now merged to
main.POST /restoreresumes it and finishes the restore.Existing and new flows
Pseudocode for each flow this PR changes. The
Existingpath ismain.Unchangedmarks branches that behave as before for Linux.Stop and the graceful-stop receipt
Start
Stopped snapshot and memory snapshot create
Restore from snapshot (
restoreSnapshot)In-place restore (
RestoreInstanceandrestoreInstance)Cold clone (fork)
Schedules
Change size
40 files, +2219 / −112 at
2703576(about 2331 churn, so above the 500-line threshold).Approximate meaningful implementation LOC: about 870 added and 95 deleted. Excluded: about 1,200 added test lines across 20 files (41 new test functions), about 115 doc lines, and mechanical churn (the
admitGuestOperationsignature change across call sites, theresumeReady*tostateReady*rename, and removal of thedarwinbuild tag fromshimconfig).snapshot_macos.go(new)finishMacOSResumemacos_identity.go(new)macos.go)snapshot.go,restore.go,standby.go,fork.go,snapshot_schedule.govz/client.go,cmd/vz-shim/server.go,hypervisor/tracing.go,hypervisor/snapshot_support.gostop.go,start.go,create.go,manager.go,types.go,macos.go,update.go,vm_config_validation.go,snapshot_compression.gomacos.golib/network/allocate.goNewUniqueMAC, so macOS clones share the allocator's uniqueness ruleBehavior
Linux. Behavior changes in one place, as fixes. If a standby fails after the VMM has stopped, the source is restored before the error is returned, instead of being left in Standby. This covers snapshot create, the public standby request, and fork from a running source. The restore uses the record standby reached, not the pre-standby disk copy. Otherwise behavior is unchanged.
macOS.
StartVM, after boot admission, so a rejected start keeps it.guests/<id>/restore-staging/. The instance directory is never renamed, so deleting an instance always deletes its staging.vz-guest-stopped, written only when the shim didn't force the stop). VMM exit alone no longer counts. A shim started by an older hypeman never writes the receipt, so its guest needs a fresh boot and stop before a stopped snapshot or clone.Testing
go build ./lib/...: passes. Run with empty placeholder files for the make-generated embedded binaries (cloud-hypervisor, guest-agent, caddy, vz-shim), which aren't built in this environment.go vet ./lib/instances/: passes.gofmt -l lib cmd: clean.go test ./lib/instances/ -run 'TestMacOS|TestRejectedMacOSStartKeepsStopReceipt|TestClearMacOSStopReceipt|TestFinishMacOS|TestAssignMacOSCloneMAC|TestGracefulShutdown' -count=1: ok.go test ./lib/instances/ -run 'TestRestoreKeepsCheckpointWhileShimLoads|TestStartRetryFinishesCloneRekey|TestRecoverSource' -count=1: ok. The first two fail without their fixes.TestRecoverSourceAfterFailedStandbyfails if the recovery skips saving the standby record, so it guards the stale-disk fix. Recovery is tested at the helper level only. The write-failure injection that reproduces a late standby failure end to end was not built.go test ./lib/hypervisor/vz/ -count=1: ok.go test ./lib/instances/ -count=1(full package): 24 integration tests fail in this sandbox. Causes: no bridge permission (create bridge: operation not permitted), nomkfs.erofs(image builds fail), and no VMs. No failures are in the new macOS unit tests. I did not run a baseline onmainfor this package.go test ./lib/hypervisor/ -count=1: 2 failures, both also present onmain.TestSocketCacheKeyChangesWhenSocketIsRecreatedfails 3/3 onmain.TestResolveProcessPIDDuringProcessChurnfails intermittently onmainand failed onmainonce.snapshot_macos_darwin_test.go, andmacos_identity_test.go, which isdarwin && arm64).GOOS=darwin go vetcannot build here because of the vz cgo dependencies.TestMacOSRestoreRecoveryRollsBackOrFinishes,TestMacOSRestoreStagingIsDeletedWithInstance: crash before and after the swap commits, and a deleted instance is not recreated.TestMacOSRestoreRetiresCheckpointBeforeResume,TestMacOSRestoreKeepsCheckpointWhenItCannotBeRetired: both fail with the old order.TestMacOSGracefulShutdownRequiresGuestStopReceipt: VMM exit without a receipt is not graceful, and an earlier run's receipt never counts. Fails on the old stop code.lib/hypervisor/vz,lib/instances, including test files): clean. The shim's receipt write has not run on a Mac.b21ccd5vs234278b: same failures, plusTestEnsureSystemFiles, which hit the Docker Hub rate limit.b21ccd5(staging location, checkpoint retirement, stop receipt), and the review remediation commits (8e04e57,2703576). They change stop-receipt derivation, receipt clearing, the restart block on restore, compression rejection on standby, and the restore metadata write.Caveats / Not included
manual_stoprestart block. Both the stopped-snapshot path (snapshot.go,startInstance) andrestoreInstanceskip clearingRestartStatus. An instance with a restart policy that was stopped, snapshotted, and restored to Running won't auto-restart after a crash.mainhas the same path, so this isn't a regression. Confirmed by reading; not reproduced at runtime. The macOS path is fixed in this PR.CompressionDelaysilently. Explicit compression is rejected.Review focus
lib/instances/snapshot_macos.go(staged swap, recovery,finishMacOSResume).lib/instances/macos_identity.go(clone identity, MAC assignment, rekey teardown).lib/instances/stop.go(receipt derivation, thepid == 0macOS branch).Stack
Shared Linux files get small hooks such as
if stored.MacOS != nil. macOS logic lives in its own files (macos*.go,*_darwin.go,snapshot_macos.go).