Skip to content

Dead-host detection + machine drain/failover - #6

Merged
blakebauman merged 1 commit into
mainfrom
feat/dead-host-detection
Jun 18, 2026
Merged

Dead-host detection + machine drain/failover#6
blakebauman merged 1 commit into
mainfrom
feat/dead-host-detection

Conversation

@blakebauman

Copy link
Copy Markdown
Owner

Context

Last core reliability gap from the audit. The control plane never noticed a host going away — a crashed or network-partitioned host kept its machines assigned indefinitely, so apps didn't recover and the scheduler could still place new machines onto a dead host. (Now much easier to operate thanks to the structured logs from #5.)

Branched from a clean main (no stacking).

What changed

  • Detection — a RunHostMonitor loop marks a host down after it misses heartbeats for FOLD_HOST_TIMEOUT (default 30s); the next heartbeat brings it back up. The scheduler already skips non-up hosts, so placement avoids dead hosts automatically.
  • Drain / failover — when a host goes down, its running machines are rescheduled onto healthy hosts: volume-backed machines stay in their AZ (the volume is detached from the dead host first so it can re-attach on the new one), and the stale host-local RAM snapshot is cleared so they cold-start where they land. Machines that aren't wanted-running are simply unplaced.
  • Scale-to-zero recoverywake() now re-places unplaced machines, so a scaled-to-zero app recovers on the next request even if its old host died.
  • Recoverystore.Heartbeat clears the down mark; control logs the down/up transitions.
  • CLIfold hosts list shows host state (up|down).

Reschedule is serialized per app (lockApp) against concurrent deploys/scale.

Caveat (documented)

A host that's alive but partitioned from control will have its machines rescheduled elsewhere. The cloud's single-attach guarantee prevents a true split for volume-backed apps, but a hard partition can briefly double-run stateless copies. Tunable via FOLD_HOST_TIMEOUT.

Tests

  • drainHost reschedules a started machine onto the live host and leaves it desired-started
  • drainHost unplaces a suspended machine and clears its stale snapshot
  • sweepHosts marks a stale host down, and a heartbeat recovers it to up

Verification

  • gofmt -l clean · go vet ./... clean · go test ./... all pass · go build -tags firecracker ./... compiles
  • make e2e-local passes (no regression in deploy/scale-to-zero/wake — wake path was touched)
  • Manual: started a local control + agent, killed the agent, and watched fold hosts list flip the host to down (with a WARN host missed heartbeats log), then back to up after restarting the agent

🤖 Generated with Claude Code

The control plane never noticed a host going away: a crashed or
partitioned host kept its machines assigned forever, so apps didn't
recover and the scheduler could still target a dead host.

- A host monitor loop marks a host `down` after it misses heartbeats for
  FOLD_HOST_TIMEOUT (default 30s); the next heartbeat brings it back `up`.
  The scheduler already skips non-up hosts, so placement avoids it.
- Draining a down host: running machines are rescheduled onto healthy
  hosts (volume-backed ones keep their AZ; the volume is detached from the
  dead host first so it can re-attach), with their stale host-local
  snapshot cleared so they cold-start where they land. Machines that
  aren't wanted-running are just unplaced.
- wake() now re-places unplaced machines, so a scaled-to-zero app recovers
  on the next request after its host died.
- `fold hosts list` shows host state (up|down).

Reschedule is serialized per app (lockApp) against deploys/scale. Caveat:
a live-but-partitioned host has its machines moved elsewhere; the cloud's
single-attach guarantee prevents a split for volume-backed apps, but a
hard partition can briefly double-run stateless copies (documented).

Tests: drain reschedules a started machine to a live host, unplaces a
suspended one (clearing its dead snapshot), and the monitor marks a stale
host down then a heartbeat recovers it. Verified end-to-end: killed a
local agent and watched the host flip down then up on restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blakebauman
blakebauman merged commit f78aaa1 into main Jun 18, 2026
0 of 2 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.

1 participant