sandbox

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 24, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package sandbox owns the in-memory registry of active sandboxes and drives their lifecycle: minting IDs and tokens, delegating container creation to the runtime, handshaking with the in-sandbox agent, and reaping sandboxes whose TTL has lapsed.

The Manager deliberately does not own HTTP — control-plane handlers (internal/api/control) call its exported methods, and the reverse proxy (internal/api/proxy) consults it for sandbox lookups. Keeping HTTP and state separate makes both easier to unit-test.

Index

Constants

View Source
const (
	// DefaultImage is the image tag used when CreateOptions.TemplateID
	// is empty. Phase 1 resolves every templateID to this.
	DefaultImage = "edvabe/base:latest"
	// DefaultDomain is the host:port edvabe reports back in the
	// Sandbox.domain field so SDKs route data-plane calls back to us.
	DefaultDomain = "localhost:3000"
	// DefaultTimeout is applied when CreateOptions.Timeout <= 0.
	DefaultTimeout = 5 * time.Minute
	// WatchdogInterval is the ticker cadence of Run's EnforceTimeouts
	// loop. Chosen to be much smaller than the smallest realistic
	// timeout while still being cheap.
	WatchdogInterval = 1 * time.Second
	// DefaultFreezeDuration caps how long a paused sandbox stays in
	// `docker pause` (holding RAM) before being demoted to `docker
	// stop` to free host memory. A day covers the "pause overnight,
	// resume next morning" case without letting forgotten sandboxes
	// hog RAM forever.
	DefaultFreezeDuration = 24 * time.Hour
	// DefaultMaxFrozen caps how many sandboxes can hold RAM via docker
	// pause at once. Further pauses demote the oldest frozen sandbox
	// first (LRU by PausedAt). Zero disables the cap.
	DefaultMaxFrozen = 10
	// DefaultStoppedGCAfter is how long a stopped (demoted) sandbox
	// lingers before the reaper destroys it to reclaim disk. A month
	// is generous; users who pause work for longer than that can
	// snapshot explicitly.
	DefaultStoppedGCAfter = 30 * 24 * time.Hour
)

Variables

View Source
var (
	// ErrNotFound is returned when a lookup targets a sandbox ID the
	// Manager has no record of.
	ErrNotFound = errors.New("sandbox: not found")
	// ErrExpired is returned when a sandbox is still in the map but its
	// ExpiresAt has lapsed — the next EnforceTimeouts pass will reap it.
	ErrExpired = errors.New("sandbox: expired")
)

Sentinel errors so handlers can discriminate without string matching.

View Source
var ErrTemplateNotFound = errors.New("sandbox: template not found")

ErrTemplateNotFound signals the resolver has no record of the given template. The manager treats this as "use the base image" so Phase 1 sandbox IDs like "base" and empty strings keep working.

Functions

func NewEnvdToken

func NewEnvdToken() string

NewEnvdToken returns "ea_" + 22 random base64url characters. Handed to envd via /init and echoed back to the SDK as envdAccessToken.

func NewSandboxID

func NewSandboxID() string

NewSandboxID returns "isb_" followed by 16 random base32 characters. Not a formal ULID — E2B clients only check the prefix, not the structure, and the prefix alone is enough insurance against parsing code that trims it.

func NewTrafficToken

func NewTrafficToken() string

NewTrafficToken returns "ta_" + 22 random base64url characters. Reported to the SDK as trafficAccessToken; edvabe does not enforce it (no real auth in v1) but SDKs expect it to be present.

Types

type Clock

type Clock interface {
	Now() time.Time
}

Clock is a tiny injection point so tests can drive EnforceTimeouts deterministically without wall-clock sleeping. Production uses realClock which defers to time.Now.

type CreateOptions

type CreateOptions struct {
	TemplateID string
	Alias      string
	Metadata   map[string]string
	EnvVars    map[string]string
	Timeout    time.Duration
	// OnTimeout controls what EnforceTimeouts does when this sandbox
	// expires. Empty string defaults to OnTimeoutKill (Phase 1 behaviour).
	OnTimeout OnTimeoutMode
	// CPUCount / MemoryMB override the template's resource limits for
	// this one sandbox. Zero leaves the template value in place; to
	// explicitly request unlimited on a capped template, pass a
	// negative number.
	CPUCount int
	MemoryMB int
}

CreateOptions is the subset of the NewSandbox request body the Manager cares about. The control-plane handler translates HTTP JSON into this.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager holds the in-memory sandbox registry and drives create / destroy / timeout enforcement. It owns no HTTP machinery — callers in internal/api consume it through its exported methods.

func NewManager

func NewManager(opts Options) (*Manager, error)

NewManager constructs a Manager. Runtime and Agent are required.

func (*Manager) Connect

func (m *Manager) Connect(ctx context.Context, id string, timeout time.Duration) (*Sandbox, error)

Connect renews the TTL on a live sandbox and returns the current snapshot. A paused sandbox is resumed through the runtime: frozen sandboxes get `docker unpause` (instant), stopped ones get `docker start` followed by a fresh agent Ping + InitAgent so envd has the access token again.

func (*Manager) Create

func (m *Manager) Create(ctx context.Context, opts CreateOptions) (*Sandbox, error)

Create mints a fresh sandbox, starts its container via the runtime, pings and initializes the in-sandbox agent, and registers the result. On any mid-flight failure the container is force-removed so nothing leaks to the runtime.

func (*Manager) Destroy

func (m *Manager) Destroy(ctx context.Context, id string) error

Destroy removes the sandbox from the registry and stops its container. Registry is the source of truth: if runtime.Destroy fails after the sandbox is removed from the map, the error propagates but the Manager's state is still coherent. Stray containers can be reaped later via the edvabe.sandbox.id label.

func (*Manager) Domain

func (m *Manager) Domain() string

Domain is the host:port edvabe reports in Sandbox responses.

func (*Manager) EnforceTimeouts

func (m *Manager) EnforceTimeouts(ctx context.Context) []string

EnforceTimeouts walks the registry and applies the three scheduled transitions:

  1. Running sandboxes whose ExpiresAt has lapsed: OnTimeoutKill destroys them, OnTimeoutPause freezes them via runtime.Pause.
  2. Frozen sandboxes paused longer than FreezeDuration, or any excess beyond MaxFrozen (oldest PausedAt first): demoted via runtime.Stop to free host RAM.
  3. Stopped sandboxes paused longer than StoppedGCAfter: destroyed to reclaim disk.

Returns the IDs touched by this sweep (killed + paused + demoted + gc'd). Runtime errors are logged implicitly by being returned from the underlying calls; failures don't abort the sweep so one stuck sandbox can't starve the others.

func (*Manager) Get

func (m *Manager) Get(id string) (*Sandbox, error)

Get returns the sandbox by ID or ErrNotFound.

func (*Manager) List

func (m *Manager) List() []*Sandbox

List returns a snapshot slice of all registered sandboxes. Order is unspecified — callers that need stable ordering should sort.

func (*Manager) Pause

func (m *Manager) Pause(ctx context.Context, id string) error

Pause freezes the container via runtime.Pause and flips the sandbox's State to paused with PauseMode=frozen. The sandbox stays in the registry — Connect unpauses it. Long-paused sandboxes are demoted to PauseStopped by the reaper; see EnforceTimeouts.

func (*Manager) PausePolicy

func (m *Manager) PausePolicy() PausePolicy

PausePolicy returns the limits the reaper enforces.

func (*Manager) Resume

func (m *Manager) Resume(ctx context.Context, id string) error

Resume brings a paused sandbox back to running without touching its TTL — unlike Connect, which also renews the deadline. Exists for dashboard / ops flows that want to inspect a paused sandbox without silently extending its life. No-op for already-running sandboxes.

func (*Manager) Run

func (m *Manager) Run(ctx context.Context, interval time.Duration)

Run drives a ticker-based timeout watchdog until ctx is cancelled. Intended to be launched as a goroutine from the serve subcommand. Pass 0 for interval to use WatchdogInterval.

func (*Manager) SetTimeout

func (m *Manager) SetTimeout(id string, timeout time.Duration) error

SetTimeout resets the sandbox TTL from the current clock. Returns ErrNotFound if the sandbox is unknown or ErrExpired if it already lapsed (typically meaning EnforceTimeouts hasn't reaped it yet).

func (*Manager) Snapshot

func (m *Manager) Snapshot(ctx context.Context, id, name string) (*SnapshotInfo, error)

Snapshot captures a container's writable filesystem layer as a new image tag via runtime.Commit. It is NOT a memory snapshot — running processes are not preserved. Pausing the sandbox first gives a consistent filesystem view but is not required.

func (*Manager) Stop

func (m *Manager) Stop(ctx context.Context, id string) error

Stop forces a sandbox into the stopped (docker stop) paused substate regardless of current state. Running sandboxes skip the freeze and go straight to stop; frozen sandboxes are unpaused first so `docker stop` can send signals to the processes; already-stopped sandboxes are a no-op. Exists so dashboard / ops flows can reclaim RAM without waiting for FreezeDuration.

type OnTimeoutMode

type OnTimeoutMode string

OnTimeoutMode controls what EnforceTimeouts does to a sandbox once its ExpiresAt has lapsed. The default (OnTimeoutKill) destroys the container; OnTimeoutPause freezes it via runtime.Pause and leaves it in the registry for a later /connect to resume. Values are the same strings the E2B SDK sends in NewSandbox.lifecycle.onTimeout.

const (
	OnTimeoutKill  OnTimeoutMode = "kill"
	OnTimeoutPause OnTimeoutMode = "pause"
)

type Options

type Options struct {
	Runtime   runtime.Runtime
	Agent     agent.AgentProvider
	Clock     Clock
	BaseImage string
	Domain    string
	// Resolver maps templateID → (image, startCmd, readyCmd). Optional:
	// when nil, every create resolves to BaseImage (Phase 1 behaviour).
	Resolver TemplateResolver
	// FreezeDuration is how long Pause keeps a sandbox in `docker pause`
	// before the reaper demotes it to `docker stop`. Zero defaults to
	// DefaultFreezeDuration. Negative disables demotion entirely.
	FreezeDuration time.Duration
	// MaxFrozen caps how many sandboxes hold RAM in docker-pause state
	// at once. Zero defaults to DefaultMaxFrozen; negative disables the
	// cap.
	MaxFrozen int
	// StoppedGCAfter is how long a demoted (stopped) sandbox lingers
	// before the reaper destroys it. Zero defaults to
	// DefaultStoppedGCAfter. Negative disables GC.
	StoppedGCAfter time.Duration
}

Options configures NewManager.

type PauseMode

type PauseMode string

PauseMode distinguishes the two kinds of paused container edvabe manages. Frozen holds RAM and resumes instantly; stopped releases RAM and requires a cold boot + agent re-init on resume. Meaningful only when State == StatePaused.

const (
	// PauseFrozen means the container is held via `docker pause` —
	// processes are suspended, memory is resident, resume is a cheap
	// `docker unpause`.
	PauseFrozen PauseMode = "frozen"
	// PauseStopped means the container was demoted to `docker stop` to
	// free host memory. Resume requires `docker start` + agent re-init,
	// and in-memory process state is lost.
	PauseStopped PauseMode = "stopped"
)

type PausePolicy

type PausePolicy struct {
	FreezeDuration time.Duration
	MaxFrozen      int
	StoppedGCAfter time.Duration
}

PausePolicy reports the configured limits used by the reaper. Exposed so the dashboard / doctor can surface the effective policy.

type Sandbox

type Sandbox struct {
	ID           string
	TemplateID   string
	Alias        string
	ContainerID  string
	AgentHost    string
	AgentPort    int
	EnvdToken    string
	TrafficToken string
	State        State
	// PauseMode is the substate when State == StatePaused. Empty
	// otherwise. See PauseMode for the tradeoffs.
	PauseMode PauseMode
	// PausedAt records when the sandbox was most recently paused. Used
	// by the reaper to demote long-frozen containers to stopped and to
	// GC long-stopped containers. Zero when State != StatePaused.
	PausedAt  time.Time
	OnTimeout OnTimeoutMode
	Metadata  map[string]string
	EnvVars   map[string]string
	CreatedAt time.Time
	ExpiresAt time.Time
	// CPUCount / MemoryMB are the resource caps applied to the
	// container. Zero means unlimited (Docker default). Sourced from
	// the template resolution and per-sandbox overrides.
	CPUCount int
	MemoryMB int
}

Sandbox is edvabe's view of one active sandbox. Fields mutated over the sandbox's lifetime (State, ExpiresAt) are guarded by Manager.mu. Callers that receive a *Sandbox from the Manager MUST treat it as read-only — use Manager methods to mutate.

type SnapshotInfo

type SnapshotInfo struct {
	Name      string
	ImageTag  string
	CreatedAt time.Time
}

SnapshotInfo is the return shape for Snapshot — the caller needs the tag to reference later and the point-in-time the snapshot was taken at for audit/logging.

type State

type State string

State is the high-level lifecycle state reported to clients.

const (
	// StateRunning means the sandbox's container is up and accepting
	// envd traffic.
	StateRunning State = "running"
	// StatePaused covers both frozen (docker pause) and stopped (docker
	// stop) containers — see PauseMode for the substate.
	StatePaused State = "paused"
)

type TemplateResolution

type TemplateResolution struct {
	ImageTag string
	StartCmd string
	ReadyCmd string
	// CPUCount caps the sandbox to this many CPU cores via cgroup
	// quota. Zero means unlimited (Docker default) — matching the
	// pre-resource-limits behaviour.
	CPUCount int
	// MemoryMB caps the sandbox's RSS. Zero means unlimited.
	MemoryMB int
}

TemplateResolution is the resolver's output.

type TemplateResolver

type TemplateResolver interface {
	Resolve(idOrAlias string) (TemplateResolution, error)
}

TemplateResolver maps a client-facing template identifier (alias or UUID) onto a concrete image tag plus the template's startCmd / readyCmd. The sandbox manager consults it at Create time — in Phase 1 this returns the base image unconditionally; Phase 3 supplies an adapter backed by the template store so user templates resolve transparently. Returning ErrTemplateNotFound falls back to the base image for backward compatibility with Phase 1 callers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL