Skip to content

fix(orchestrator): persist checkpoint snapshot when resume-fresh fails - #3666

Closed
AdaAibaby wants to merge 1 commit into
e2b-dev:mainfrom
AdaAibaby:fix/checkpoint-persist-snapshot-on-resume-failure
Closed

AdaAibaby wants to merge 1 commit into
e2b-dev:mainfrom
AdaAibaby:fix/checkpoint-persist-snapshot-on-resume-failure

Conversation

@AdaAibaby

Copy link
Copy Markdown
Contributor

Summary

Fixes the checkpoint half of #3658: on self-hosted Embed, a checkpoint whose replacement sandbox fails to start loses the snapshot.

checkpointResumeFresh snapshots the running sandbox and caches it locally, then resumes a fresh replacement sandbox from the produced build. If that replacement fails to start — e.g. mmap memfd: cannot allocate memory when the host hugepage pool is exhausted (exactly the reproduction in #3658) — the handler returned before runCheckpointUpload ran. The already-valid snapshot was never uploaded to remote storage, the build was marked failed, and restoring it returned 404: template not found.

Change

On the resume-failure path, upload the cached snapshot before returning the error, so it stays restorable.

This is safe because resume-fresh never defers the rootfs export and never keeps a CoW memory window — snapshotAndCacheSandbox is called with deferRootfsExport=false and maintainSandbox=false, so the snapshot bytes are fully materialized in the local cache by the time the resume is attempted. The upload is best-effort: a failure is logged and never masks the underlying resume error returned to the caller.

Scope

This addresses the checkpoint scenario only. The pause scenario in #3658 (a failed rootfs-diff fsync during pause) requires a separate, larger change: the API deletes the routing entry and store record before the orchestrator Pause RPC runs, so keeping the sandbox recoverable there needs an API↔orchestrator contract change (gate teardown on snapshot durability, extend the existing restoreOnRefusal path to snapshot failures). That will be a follow-up PR, since it touches the in-place-checkpoint lifecycle and needs KVM integration testing.

Testing

  • Orchestrator pkg/server builds clean with the change.
  • The resume-failure path itself exercises real Firecracker (ResumeSandbox), so full validation is via integration tests on a KVM host; the change is additive and only runs on an error path that today discards a valid snapshot, so it cannot regress the success path.

@cla-bot

cla-bot Bot commented Sep 28, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @ada on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

checkpointResumeFresh caches the snapshot successfully, then resumes a
fresh replacement sandbox from it. If that replacement fails to start
(e.g. "mmap memfd: cannot allocate memory" when the host hugepage pool
is exhausted), the handler returned before runCheckpointUpload ran, so
the already-valid snapshot was never uploaded to remote storage. The
build was left marked failed with local-only bytes, and restoring it
returned 404: template not found.

Upload the snapshot on the resume-failure path before returning the
error, so it stays restorable. This is safe because resume-fresh never
defers the rootfs export and never keeps a CoW memory window
(snapshotAndCacheSandbox is called with deferRootfsExport=false and
maintainSandbox=false), so the snapshot bytes are fully materialized in
the local cache by the time the resume is attempted. The upload is
best-effort: a failure is logged and never masks the resume error.

Addresses the checkpoint half of e2b-dev#3658.

Signed-off-by: AdaAibaby <ada@users.noreply.github.com>
@AdaAibaby
AdaAibaby force-pushed the fix/checkpoint-persist-snapshot-on-resume-failure branch from 6dc76a3 to 0c073e3 Compare September 28, 2026 11:58
@cla-bot cla-bot Bot added the cla-signed label Sep 28, 2026
AdaAibaby pushed a commit to AdaAibaby/infra that referenced this pull request Sep 29, 2026
A pause whose snapshot failed after the guest was suspended (e.g. a
rootfs-diff fsync EIO, or a memfd ENOMEM under host memory/hugepage
pressure) destroyed the sandbox: Server.Pause armed an unconditional
deferred stop before snapshotting, so the error path tore down the
already-suspended VM, and the API had already removed the routing entry
and store record. The sandbox was left unrecoverable with no snapshot
(e2b-dev#3658).

Behind PauseRefusalRestoreFlag (off by default, so today's
destroy-on-failure behaviour is unchanged), keep the sandbox recoverable:

- sandbox: decouple the resume-on-error cleanup from WithMaintainSandbox
  via a new WithResumeOnFailure() option. It arms the same resume-in-place
  cleanup (health checks restarted, guest clock re-synced) that the
  in-place checkpoint uses, but only on failure - a successful pause still
  suspends and leaves the VM for the caller to stop.
- Server.Pause: pass resumeOnFailure when the flag is on, and arm the
  deferred stop only after the snapshot succeeds. On snapshot error the
  guest has been resumed in place, so re-register it in the live map
  (MarkRunning) and return FailedPrecondition with a "sandbox preserved"
  marker. ErrSandboxLost (the resume itself failed) falls through to the
  destroy path and returns Internal unchanged.
- api: classify that FailedPrecondition+marker as ErrPausePreservedSandbox
  (pause_instance.go) and map it in DeleteInstance to the existing
  restoreRefusedPause path, restoring the store record and route instead
  of removing them.

Tests: TestSnapshotInstance_PreservedSandboxIsClassified and
_PlainFailedPreconditionNotPreserved cover the API classification (a
plain FailedPrecondition is not misclassified). All three packages build;
the full internal/orchestrator suite passes.

End-to-end validation with the flag enabled against real Firecracker
(inject a snapshot fault, assert the sandbox survives and stays routable)
requires a KVM host and is a follow-up.

Addresses the pause half of e2b-dev#3658 (checkpoint half: e2b-dev#3666).

Signed-off-by: adababys <8872412+adababys@users.noreply.github.com>
@svalleru svalleru closed this Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants