Skip to content

feat(vm): atomic hibernate; restore accepts stopped VMs#87

Merged
CMGS merged 5 commits into
masterfrom
feat-atomic-hibernate
Jul 5, 2026
Merged

feat(vm): atomic hibernate; restore accepts stopped VMs#87
CMGS merged 5 commits into
masterfrom
feat-atomic-hibernate

Conversation

@CMGS

@CMGS CMGS commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Closes #85.

What

  • vm hibernate VM [--name --description] — atomic pause → capture → terminate: HibernateSequence shares the capture skeleton with SnapshotSequence (extracted prepareSnapshot/finishSnapshot) but ends the pause window by force-terminating the VMM instead of resuming, then cleans runtime files and flips the record to stopped (metering interval closes via the existing UpdateStates path). Capture failure resumes the VM; terminate failure marks it error. Exposed as the optional hypervisor.Hibernator interface (the Direct pattern), implemented by CH and FC via their shared snapshotSpec.
  • Restore accepts stopped VMsResolveForRestore allows stopped alongside running. Mechanically nothing else changes: RestoreSequence cold-spawns a fresh VMM from the snapshot either way, KillForRestore already tolerates ErrNotRunning, and emitRestoreComputeStop no-ops on an already-closed interval, so the ledger stays sane.
  • The snapshot-name preflight moved to cmdcore.EnsureSnapshotNameFree (shared by snapshot save and vm hibernate — hibernate must fail on a taken name before the VM is stopped).

Why

Sandbox hibernation: idle VM releases memory, resumes later with identity intact. The previous savestop composition loses whatever the guest writes after the snapshot point; restore refused stopped VMs outright.

Verified (bare-metal node, FC guest, silkd-baked image)

  • vm hibernate: 455ms, state=stopped, zero VMM processes (memory freed)
  • vm restore on the stopped VM: 27ms; /etc/machine-id unchanged, a tmpfs marker written before hibernate survives (memory state intact), the in-guest daemon keeps running
  • Full sandbox control-plane e2e (golden build → warm pool both lanes → claim tiers → verb smoke → reap → restart reconcile) passes with this binary — the snapshot/clone pipeline is unaffected
  • make lint test green (GOOS linux+darwin matrix); new TestResolveForRestoreStates covers the guard

CMGS added 2 commits July 5, 2026 23:56
vm hibernate captures like snapshot save but terminates the VMM inside
the pause window instead of resuming, so the snapshot point and the
stop coincide and nothing written after the snapshot is lost on resume.
Restore's running-only guard relaxes to running-or-stopped: the
sequence cold-spawns a fresh VMM from the snapshot either way and the
kill step already tolerates a dead one. Restore paths keep the
entropy-only reseed, so a resumed VM retains its machine identity.
HibernateSpec carries the terminate hook (now sharing the sequence's
keep-alive client) and runtime files, matching the Spec-struct pattern;
terminateVMM dedups the restore/hibernate kill closures per backend;
runWrapped collapses the four Wrap-or-direct dispatch sites; a state
flip failure after a successful kill downgrades to warn-and-continue so
a transient DB error cannot delete the only memory image; restore of a
non-running VM now runs Start's network self-heal (host-reboot resume);
cmdcore.CaptureSnapshot folds the shared save/hibernate tail and drops
the double-prefixed error wrap.
@CMGS

CMGS commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Review round applied (reuse/simplification/efficiency/altitude): HibernateSpec struct (terminate hook shares the sequence's keep-alive client), per-backend terminateVMM dedup with the restore kill path, runWrapped for the four Wrap dispatch sites, warn-and-continue on the post-kill state flip (a transient DB error must not delete the only memory image), Start-parity network self-heal when restoring a non-running VM (host-reboot resume), and cmdcore.CaptureSnapshot folding the save/hibernate tail. Re-verified on hardware: hibernate 458ms / restore-on-stopped 29ms, identity + tmpfs marker intact; full control-plane e2e regression green with the updated binary.

The previous order terminated the VMM and marked the VM stopped before
the snapshot reached the store; a failed save (disk full, snapshot DB
error) then closed the TarDirStreamWithRemove stream, deleting the only
memory image — VM stopped, no resume point. Persist now runs inside the
pause window and the VMM dies only after it succeeds: any failure up to
that point resumes the VM and the command simply fails. Hibernator
takes the persist callback so the hypervisor layer stays ignorant of
snapshot storage. Also updates README/KNOWN_ISSUES for hibernate and
restore-on-stopped.
@CMGS

CMGS commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the unsafe save-failure path: persist now happens inside the pause window and the VMM is terminated only after the snapshot is durably stored — a failed save (disk full, DB error) resumes the VM and the command fails with nothing lost. Verified on hardware with fault injection: a 1MB tmpfs mounted over the snapshot store made hibernate fail with ENOSPC and the VM stayed running (guest state intact, memory marker readable); after unmounting, hibernate 326ms / restore 35ms / marker intact. Full control-plane e2e regression green. README and KNOWN_ISSUES updated for hibernate and restore-on-stopped.

RecordSnapshot runs before persist, so a persist failure resumed the VM
but left a snapshot id in the record that maps to nothing. Drop it via
best-effort UnrecordSnapshot on that path, and cover both hibernate
sequence outcomes with unit tests driven by a stub VMM process.
@CMGS

CMGS commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Persist-failure rollback added: UnrecordSnapshot (best-effort, warn on failure) drops the just-recorded snapshot id when persist fails, so vm inspect never shows an id that maps to no stored snapshot. The safety path now has automated regression coverage: TestHibernateSequencePersistFailureResumesAndRollsBack drives the real HibernateSequence against a stub VMM process (argv[0]+socket-arg satisfy the liveness check) and asserts resume happened, terminate did not, state stayed running, and the snapshot id set is clean; TestHibernateSequenceSuccess covers the terminate/stopped/recorded side. Note: the same stale-id window pre-exists in snapshot save (Create fails at the cmd layer after finishSnapshot recorded the id, no rollback) — cosmetic there for the same reason, left out of this PR's scope.

…st tail

cmd.Start returns before bash execs into the argv[0]-renamed tail, so
the cmdline liveness check raced on Linux (green on darwin only via the
no-/proc fallback) — poll until the check passes. Add the third
hibernate outcome test (terminate failure marks error, snapshot stays
recorded). PersistSnapshotStream dedups the save/hibernate persist
tail; finishSnapshot inlined into SnapshotSequence.
@CMGS CMGS merged commit 845640f into master Jul 5, 2026
6 checks passed
@CMGS CMGS deleted the feat-atomic-hibernate branch July 5, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hibernate primitives: atomic snapshot-and-stop, and restore of a stopped VM

1 participant