Repository navigation
Controller restore-path resilience: stop mass-pruning sandboxes on restart and restore failures #301
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Problem
Intermittent CI failures across the stack trace back to a multi-layer reliability gap in the forkd controller's snapshot restore path. The failure chain:
restore_many_withfails — firecracker VMs fail to start during restore, loggingsocket /tmp/forkd-daemon-<tag>-<id>/child-N.sock never appeared within 10s(forkd-vmmwait_for_sock, lib.rs ~line 902). Observed to be systemic (affects multiple image tags, not one).- Controller restart prunes ALL sandboxes — on startup,
Registry::reconcile()(state.rs:213) prunes every sandbox entry whose PID is no longer alive. After a watchdog-triggered restart, every running sandbox is pruned (observed: 22 pruned in one incident), invalidating all upstream lease references in a single stroke. - Orphaned firecracker processes survive backend restarts — when the upstream lease manager restarts, its
Shutdown()path does not fully kill all sandbox VMs via the forkd API, leaving orphaned firecracker processes. These orphans cause resource contention (tap device conflicts, netns conflicts, fd limits) that prevents newrestore_many_withoperations from succeeding, producing a cascade.
Root cause analysis
- The 10-second
wait_for_socktimeout (Duration::from_secs(10)at lib.rs:980 and :1486) is a fixed budget with no load-awareness. Under contention from orphaned VMs or parallel restore bursts, firecracker processes take longer than 10s to open their API socket, and the wait bails out. reconcile()is a blunt instrument: it cannot distinguish "VM died unexpectedly" from "VM is alive but the controller just restarted." On restart, all live VMs become unreconcilable (nolive_vmshandles can be reconstructed from registry data alone) and are pruned.- There is no pre-restore orphan detection:
restore_many_withdoes not check whether firecracker processes from a prior (crashed) run still hold tap devices or netns slots before attempting a new restore.
Resource exhaustion ruled out
The host has ample capacity during failures: 62 GB RAM (58 GB available), 22 GB disk free, 32 GB tmpfs. The 22 firecracker processes and 4m37s CPU / 2.7 GB peak of the controller are well within limits. The failures are not resource exhaustion — they are startup-path contention and blunt-restart side effects.
Proposed fix areas
1. Restore-path resilience
- Investigate why firecracker processes fail to open their API socket within 10s under load — tap/netns conflicts from orphaned VMs, fd limits, or controller-internal serialization.
- Make the
wait_for_socktimeout configurable and/or load-aware (longer budget under concurrent restore bursts), rather than a hardcoded 10s. - Add per-VM error reporting so partial
restore_many_withfailures surface which specific child failed, rather than a single bail that loses all children.
2. Graceful reconcile / restart recovery
- Distinguish "controller crashed with surviving VMs" from "VM died" during
reconcile(). Surviving firecracker processes with valid sockets are manageable orphans, not stale entries to prune — either reattach (if feasible) or kill targeted PIDs before pruning the registry. - (In progress on branch
fix/controller-restart-recovery—kill_orphans()kills orphaned firecracker processes on startup afterreconcile(), with PID-reuse guards and bounded death polling. Tracked separately; this issue covers the broader restore-path resilience.)
3. Orphan detection before restore
- Before
restore_many_with, detect and reap orphaned firecracker processes (processes not tracked by a livelive_vmsentry) holding tap devices or netns slots, so a new restore is not doomed by a prior crash's leftovers.
4. Watchdog: targeted recovery instead of mass kill
- The operational watchdog (
forkd-spawn-watchdog.sh, deployed on the server, not in this repo) currentlypkill -9 firecracker— killing all running VMs, not just the failed ones. A single-VM restore failure becomes a full-pool outage. - A graduated response: first attempt soft restart of just the failed restore, only escalate to controller restart on repeated failures. Kill firecracker processes associated with the failed daemon dir only, not all firecracker processes.
- (The watchdog script is operational tooling living outside this repo; this issue tracks the controller-side changes that would enable targeted recovery — per-VM error reporting, orphan detection, graceful reconcile.)
Acceptance criteria
- Single VM restore failure does NOT prune all running sandboxes
restore_many_withreports per-VM failures so partial successes are not lost- Orphaned firecracker processes from a prior crash are detected and reaped before a new restore is attempted
wait_for_socktimeout is no longer a hardcoded 10s under load- A controller restart does not mass-prune VMs that are still alive
Related
- The upstream lease manager has a companion issue for its side of the cascade (runner retry on 410 Gone, graceful drain before restart, orphan detection on backend startup).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
fix(vmm): restore-path resilience for issue deeplethe#301
fix(vmm): apply review fixes for deeplethe#301
fix(vmm): review fixes for deeplethe#301 — indexed success, valid tes…
fix(vmm): review fixes for deeplethe#301 — indexed success, valid tes…
fix(vmm): review fixes for deeplethe#301 — indexed success, valid tes…
fix(vmm): review fixes for deeplethe#301 — indexed success, valid tes…
WaylandYang commentedon Aug 15, 2026
Maintainer status: #302's VMM-side implementation was reviewed and approved at \2751b9, but it now conflicts with current \main\ after #281. The author has been asked to rebase while preserving both the shared-TAP ownership lifecycle and #302's timeout/partial-failure/orphan-metric semantics. Even after #302 lands, this issue must remain open: #302 covers the per-VM reporting and configurable-timeout portions, while orphan reap/gating and restart recovery still depend on #299/#304 and further controller work.
fix(vmm): restore-path resilience for issue deeplethe#301 (squashed)
fix(vmm): restore-path resilience for issue deeplethe#301 (squashed)
fix(vmm): restore-path resilience for issue deeplethe#301 (squashed)
fix(vmm): reap the Firecracker child on every failed boot
WaylandYang commentedon Sep 14, 2026
Status update: #315 is merged into
dev.Vm::bootnow reaps its Firecracker child on every failed boot, which closes one source of orphans (a live FC holding the tap afterwait_for_socktimes out). This issue stays open for the remaining restart-recovery and restore-gating work.