Skip to content

experimental: upstream open-PR cherry-picks under test (tracking) - #4

Open
tiago-mitralab wants to merge 9 commits into
stablefrom
experimental
Open

tiago-mitralab wants to merge 9 commits into
stablefrom
experimental

Conversation

@tiago-mitralab

@tiago-mitralab tiago-mitralab commented Jul 9, 2026 •

Copy link
Copy Markdown

Tracking PR for the experimental branch. Do not squash-merge as a block, promote one cherry-pick at a time.

Where this sits

The fork runs three branches, and this PR only makes sense against the middle one:

  • main is a plain mirror of e2b-dev/infra. Nothing of ours lands here, and no host should ever run it: it has no ublk.
  • stable is main plus our permanent patches (ublk rootfs provider, MAX_STARTING_INSTANCES_PER_NODE, MAX_SANDBOXES_PER_NODE, and the multi-node static discovery). It is the default of the bootstraps.
  • experimental is stable plus the cherry-picks below, validated on a host before anything is promoted.

Syncing is a fast-forward on main, then a rebase of stable over it, then a rebase of experimental over stable. The recurring conflict is always the same, sandbox.NewFactory, because upstream keeps changing its signature and ublk adds a parameter to it.

Cherry-picks

Upstream status checked on 2026-07-26.

Still open upstream, so still ours to carry:

Closed upstream without merging:

Merged upstream, already gone from here:

Notes

envd follows whatever version upstream ships; the bumps that came with e2b-dev#3099 and e2b-dev#3145 were dropped in the cherry-picks.

The branch is force-pushed and re-rebased on every advance of main, so a host must pin a SHA and never this branch name.

@tiago-mitralab
tiago-mitralab changed the base branch from main to stable July 26, 2026 14:22
@tiago-mitralab
tiago-mitralab force-pushed the experimental branch 2 times, most recently from 3e85565 to 81cb62a Compare August 31, 2026 14:52
adababys and others added 8 commits October 21, 2026 19:27
The UFFD handle goroutine had a TODO comment stating that the sandbox
should be killed when handle() fails. In fact, this is already
implemented: u.exit.SetError() triggers the sandbox exit watcher in
sandbox.go (line ~1093) which calls sbx.Stop().

- Remove the stale TODO comment
- Add error logging when handle() fails for better observability
- Add unit tests covering handle failure behavior (exit error
  propagation, readyCh closure, handler error state, initial state,
  socket creation)

(cherry picked from commit 3e30b03)
WaitForExit was defined but never called, leaving the API-layer evictor as
the sole mechanism to kill expired sandboxes.  If the API service restarts
the evictor goroutine stops and VMs accumulate indefinitely on nodes.

Two changes:

1. setupSandboxLifecycle (sandboxes.go): replace sbx.Wait with
   sbx.WaitForExit so the lifecycle goroutine races against the sandbox TTL.
   On timeout, Stop() is called explicitly before the normal Close/cleanup
   path runs.

2. WaitForExit (sandbox.go): replace the one-shot time.After with a
   time.NewTimer loop that re-checks endAt after each fire.  This means a
   KeepAlive that calls SetEndAt mid-flight correctly resets the node-side
   deadline instead of being silently ignored.

Fixes e2b-dev#3193

(cherry picked from commit 0a823f1)
5 cases covering the two code changes in the parent commit:

- AlreadyExpired: endAt in the past returns error immediately
- ExitBeforeTTL: clean FC exit before TTL returns nil
- ExitWithError: FC exits with error, error is wrapped and returned
- KeepAliveExtendsTTL: SetEndAt extension resets the timer loop;
  sandbox is NOT killed at the original deadline
- ContextCancelled: ctx cancel returns nil without killing

All pass with -race.

(cherry picked from commit 4826a8e)
When creating a sandbox, merge the template metadata's Context.EnvVars
(which contains Docker image ENV directives) into the sandbox config's
Envd.Vars before sending them to envd via POST /init.

User-provided env vars from the SDK create() call take precedence over
template defaults. Per-command env vars from process.start() still
override both.

This fixes both the standard resume path and the filesystem-only cold
boot (reboot) path, where DefaultUser and DefaultWorkdir were already
restored from template metadata but EnvVars was not.

Closes e2b-dev#2268

(cherry picked from commit 5bfd55e)
… as killed

The node-side TTL from the WaitForExit cherry-pick fired at the sandbox end
time, the same moment the API evictor acts, and called Stop with no stop
reason. Two consequences: a sandbox the API meant to pause (auto-pause) could
be killed first, since the node does not know about auto-pause, and every
expiry it enforced read as a crash (ERROR, crashed label, and the per-team
crash counter upstream added in 44932b6).

The lifecycle now waits nodeTTLGrace (2 minutes) past the end time, so with
the API up the evictor decides between pause and kill and the node only acts
when the API never did. WaitForExit returns ErrSandboxTTLExpired for an
expiry, and the lifecycle records StopReasonKilled and logs at Info. When the
timer and the Firecracker exit are ready together, the exit wins, so a crash
never reads as a TTL.

The uffd handler logged its ERROR before upstream 394ed94 classified a stop
during accept or handshake as benign, so a clean stop still logged an error.
The log now follows the classification.
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