Skip to content

[codex] Fix fork reflink follow-up - #18

Merged
arslnb merged 1 commit into
mainfrom
codex/pr-7-ready
Jul 5, 2026
Merged

[codex] Fix fork reflink follow-up#18
arslnb merged 1 commit into
mainfrom
codex/pr-7-ready

Conversation

@arslnb

@arslnb arslnb commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Replacement for #7 with the original fork-performance change rebased onto current main and the review blockers addressed.

What changed:

  • Routes fork/golden/private-disk copies through the fail-loud reflink helper.
  • Makes require_reflink=true use cp --reflink=always, so operator opt-in cannot silently fall back to full multi-GB copies.
  • Captures the spawned child process id immediately in the fork path, so staging failures after spawn still clean up the child Firecracker process.
  • Preserves current main egress-policy handling and carries the hostname-safe preview URL acceptance assertion.

Validation:

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p sandboxd

Fork latency was paying two stacked, avoidable costs:

1. Parent always took a Full snapshot (~23s for a 2 GB guest) on every fork,
   even when a base mem.file from a prior snapshot already existed. Now fork
   reuses standby's Diff-vs-Full decision: when the parent already has a base
   mem.file and dirty tracking is live (snapshotted=true), it takes a Diff —
   only pages dirtied since the last snapshot are rewritten in place (~0-7s),
   leaving a complete current image. A fresh or golden-restored parent (whose
   mem.file is a shared hardlinked inode that must never be diffed in place)
   correctly falls to a Full first to create the base.

2. Every per-VM artifact copy (child mem.file / rootfs / overlay, golden
   staging, private-disk restore) shelled out to `cp --reflink=auto` and
   ignored the result. On a reflink-capable FS that is instant CoW; on a
   non-reflink FS (plain ext4, tmpfs) it silently degrades to a full
   byte-for-byte copy — the measured ~58s-per-fork cost on the ext4 node — and
   a failed copy could masquerade as a complete artifact. All sites now route
   through one `reflink_copy` helper that bails on a non-success cp status, and
   a new `require_reflink` config gate (off by default) probes the data FS once
   at startup and FAILS CLOSED rather than emit a multi-GB full copy when the
   operator asserted reflink support. The daemon logs the reflink capability at
   boot; provision-node.sh arms the guard automatically on a reflink-capable
   node so a later FS regression surfaces loudly instead of regressing latency.

Also hardens the fork hot path: child bringup now runs inside a guard that
reclaims the live child microVM, its tap, and jail dir on any error, mirroring
create()'s booted-match cleanup (a partial fork previously leaked these). Adds
a `fork` benchmark path (cold-parent + child clone + paused load + resume) and
docs (DEPLOY config table, RUNBOOK §10 Fork & reflink with verify/remediation).

Expected win: cold fork parent snapshot ~23s -> ~0-7s via Diff once a base
exists; per-VM copy ~58s -> instant CoW on a reflink FS.

Proven here: cargo build, 54 mock-runtime tests pass, clippy clean, fmt clean.
Not measured here: real boot/fork wall-clock — this box has no KVM/Firecracker,
so the latency numbers are the documented prior measurements, pending a node.
@arslnb
arslnb merged commit 3f8006d into main Jul 5, 2026
4 checks passed
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