Skip to content

[codex] fix: make macOS VM creation transactional - #34

Draft
czmDeRepository wants to merge 7 commits into
cocoonstack:masterfrom
czmDeRepository:codex/macos-transactional-create
Draft

[codex] fix: make macOS VM creation transactional#34
czmDeRepository wants to merge 7 commits into
cocoonstack:masterfrom
czmDeRepository:codex/macos-transactional-create

Conversation

@czmDeRepository

Copy link
Copy Markdown
Contributor

Summary

  • serialize create, run, clone, start, stop, and remove operations by VM name using a lock outside the removable VM directory
  • clean up partial VM directories, QEMU processes, qemu-nbd helpers, VNC proxies, and owned networking when creation fails or is canceled
  • verify the daemonized QEMU PID before committing it, and let start adopt a QEMU process when the CLI was interrupted before saving the PID

Root cause

VM creation spans filesystem scaffolding, OpenCore/NBD injection, networking, QEMU daemonization, and record persistence. Cancellation or failure between those steps could leave partial state. The old lock file also lived inside the directory removed by rm, allowing concurrent operations to lock different inodes.

Impact

Failed or interrupted VM creation becomes retryable without manual cleanup, concurrent lifecycle commands remain serialized, and an already-running guest is not duplicated when its PID record was not committed.

Dependency

This is a stacked draft based on the changes proposed in #30 and #31. Those commits are included temporarily so the combined behavior can be tested. After #30 and #31 merge, this branch will be rebased onto master, leaving only the transactional-create commit for review.

Validation

  • GOWORK=off go test ./...
  • make fmt-check vet lint
  • lint and vet passed for both Linux and Darwin targets

@CMGS CMGS left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the transactional design is good: lock moved to <parent>/.locks/<name>.lock via flock.NewTransient so rm can't unlink the inode mid-hold and split mutual exclusion; cleanupFailedVM reaps helpers/net/qemu on every create/run failure with an uncanceled bounded context; launch now verifies the pid before committing; start adopts a qemu that daemonized before its pid was saved. combined-branch gates: lint (linux+darwin) 0, go test, gofmt all green.

stacked-draft caveat: this carries the #30 and #31 commits, so 3 of the 4 asl findings (qemu/inject.go:29, qemu/inject_test.go:116, cmd/vm/handler_test.go:73) and the inject comment nits belong to those PRs — already flagged there, they'll clear when you rebase onto master after #30/#31 merge. the items below are the ones new to c4d89af.

Comment thread cmd/vm/lifecycle.go
// cleanupFailedVM makes create/run transactional. It uses an uncanceled,
// bounded context so SIGTERM-driven command cancellation still reaps helpers,
// networking and any QEMU process started before the record was committed.
func cleanupFailedVM(cmd *cobra.Command, dir string, r *record) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asl (both GOOS): cleanupFailedVM is a standalone func sitting between the Handler method set (create ... launch). move it below the Handler methods with the other package funcs.

Comment thread cmd/vm/utils.go
return nil
}

func cleanupQEMUForPath(ctx context.Context, path string) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse: this is #31's CleanupNBDForPath with a different binary + grace — same FindVMMByCmdline → TerminateProcess loop → errors.Join. collapse both into one terminateByCmdline(ctx, binary, path, grace) (and fold #31's into it when you rebase) instead of two copies drifting apart.

Comment thread cmd/vm/utils.go
}

// withVMLock serializes concurrent lifecycle ops on one VM (vm.json is read-modify-write).
// withVMLock serializes concurrent lifecycle ops on one VM. The lock lives

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment budget: 3 lines → 1. the inode-split WHY is the keeper:
// lock lives outside the VM dir so rm can't unlink the inode a waiter still holds and split mutual exclusion.

Comment thread cmd/vm/utils.go
return dir, overlay, ovmfVars, digest, nil
}

// resetIncompleteVMDir removes state left before vm.json was committed. It

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment budget: 3 lines → 1, e.g. // remove pre-commit VM state; refuses a dir a live qemu still references.

Comment thread cmd/vm/utils.go
return utils.VerifyProcessCmdline(r.PID, qemuBinary, r.Disk)
}

// adoptRunningQEMU repairs a record whose launch was interrupted after QEMU

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment budget: 3 lines → 1, e.g. // adopt a qemu that daemonized before its pid was saved; >1 match is corruption, not a guess.

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.

2 participants