process

package
v0.10.9 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// LabelManaged marks every resource launched by this control plane.
	// Value is always "true".
	LabelManaged = "shinyhub.managed"
	// LabelSlug is the app slug that owns the replica.
	LabelSlug = "shinyhub.slug"
	// LabelReplicaIndex is the zero-based replica index within the pool.
	LabelReplicaIndex = "shinyhub.replica_index"
	// LabelTier is the tier name the replica belongs to.
	LabelTier = "shinyhub.tier"
	// LabelProvider is the runtime provider name (e.g. "docker", "fargate").
	LabelProvider = "shinyhub.provider"
	// LabelDeploymentID is the deployment row ID that placed this replica.
	LabelDeploymentID = "shinyhub.deployment_id"
	// LabelAppVersion is the app version string stamped at deploy time.
	LabelAppVersion = "shinyhub.app_version"
	// LabelContentDigest is the SHA-256 content digest of the deployed bundle.
	LabelContentDigest = "shinyhub.content_digest"
	// LabelPort is the port the app binds inside the replica so recovery can
	// rebuild the full route URL from the resource alone.
	LabelPort = "shinyhub.port"
	// LabelMaxSessions is the per-replica active-connection hard cap persisted on
	// the container so re-adoption after an agent restart restores the same limit.
	// Value is the decimal integer cap; absent or unparseable means no cap.
	LabelMaxSessions = "shinyhub.max_sessions"
)

Label keys stamped on every managed replica container or task. All three backends (docker, remote_docker, fargate/ECS) write and read these same keys so the lifecycle layer can reconcile any backend identically.

View Source
const DefaultLogMaxSize = 5 << 20

DefaultLogMaxSize is the per-app log file size cap (5 MB). When exceeded, the file is rotated to app.log.1 and a fresh file is started.

View Source
const DefaultTier = "local"

DefaultTier is the tier name a replica runs under when StartParams.Tier is empty. Single-node deployments use exactly this one tier.

View Source
const SynthesizedProjectMarker = ".shinyhub-synthesized-project"

SynthesizedProjectMarker is a sentinel EnsureProject drops next to a pyproject.toml it generated from a requirements.txt. It distinguishes a synthesized project (valid only where this host prepared the deps and synced the .venv) from one the author shipped (valid everywhere).

Variables

View Source
var ErrNoLiveWorker = errors.New("no live worker for tier")

ErrNoLiveWorker is returned (wrapped) when a tier-bound remote runtime has no live worker to place a replica on. The watcher treats it as a zero-cost failure: a missing worker is an infrastructure gap, not the app's fault, so it must not consume the crash-restart budget.

View Source
var ErrReplicaAlreadyRunning = errors.New("replica already running")

ErrReplicaAlreadyRunning is returned (wrapped) by Manager.Start when the target slug+index slot is already running. The watcher treats it as zero-cost: a re-placement that races a slot already (re)filled is a no-op, not a failure.

View Source
var ErrReplicaNotFound = errors.New("replica not found")

ErrReplicaNotFound is returned (wrapped) by Manager.StopReplica when the slug+index slot has no live entry. Callers that distinguish an already-gone replica from a real stop failure (e.g. autoscale scale-down) match this sentinel: a missing entry is benign, while any other error means the replica may still be running and its control-plane state must be left intact.

View Source
var ErrReplicaNotSuspended = errors.New("replica not suspended")

ErrReplicaNotSuspended is returned (wrapped) by Manager.Resume when the target slot is not in a suspended state, so there is nothing to resume.

View Source
var ErrRuntimeNotSnapshotter = errors.New("runtime does not support snapshot")

ErrRuntimeNotSnapshotter is returned (wrapped) by Manager.Suspend/Resume when the replica's tier runtime does not implement Snapshotter. Callers fall back to Stop (hibernate) or a cold RunReplica (wake).

Functions

func CheckUV

func CheckUV() error

CheckUV verifies that the uv binary is available in PATH.

func EnsureProject added in v0.8.12

func EnsureProject(ctx context.Context, dir string) error

EnsureProject converts a requirements.txt-only Python app into a uv project so it gains a native uv.lock (fully pinned, hashed, requires-python-aware) and launches in project mode. Reproducibility then comes from one mechanism - uv.lock - for both author-provided and requirements-based apps.

It is a no-op when a pyproject.toml is already present (the author's, or a prior conversion) or when there is no requirements.txt to convert. On a failed `uv add` it removes the half-built project so the app falls back cleanly to requirements mode rather than launching against an incomplete environment.

func EnsurePython

func EnsurePython(version string) error

EnsurePython runs `uv python install <version>` if version is non-empty.

func IsSynthesizedProject added in v0.8.12

func IsSynthesizedProject(dir string) bool

IsSynthesizedProject reports whether the pyproject.toml in dir was generated by EnsureProject (rather than shipped by the app author).

func SanitizedEnv added in v0.5.1

func SanitizedEnv() []string

SanitizedEnv returns an allow-listed subset of the current process environment. It is the single source of truth for the env base of every app-controlled code path: app processes, dependency installation (uv/renv), and post-deploy hooks. Server secrets (SHINYHUB_AUTH_SECRET, the deploy token, OAuth/OIDC client secrets, and cloud credentials such as AWS_SECRET_ACCESS_KEY) must never reach code that a deployer can influence, so only known-safe variables pass through.

func Sync

func Sync(ctx context.Context, dir string) error

Sync runs `uv sync` in dir if a pyproject.toml is present, creating/updating the .venv. For requirements.txt-only projects, dependency installation is handled lazily by `uv run --with-requirements` at process start.

func SyncR

func SyncR(ctx context.Context, bundleDir string) error

SyncR runs renv::restore() in bundleDir to install R package dependencies. It is a no-op when renv.lock does not exist (app manages its own packages). The caller adds the "renv restore:" prefix that deployfail classifies on.

Types

type CgroupReadopter added in v0.9.0

type CgroupReadopter interface {
	// ReadoptCgroup re-registers the deterministic app-<slug>-<index> cgroup for
	// an adopted pid when it exists and still contains the pid, and seeds its OOM
	// baseline. Best-effort: returns nil when there is no such cgroup (no limit
	// was set, or warm-wake is off and no base exists).
	ReadoptCgroup(slug string, index, pid int) error
}

CgroupReadopter re-registers a replica's per-app resource-limit cgroup after a server restart, INDEPENDENT of warm-wake. Without it, an adopted replica that has a memory/CPU limit (but no warm-wake) loses its cgroup mapping, so the runtime can neither tear it down nor detect an OOM-kill for it. Only the native runtime implements it; container runtimes hold this state in their daemon.

type ContainerInfo

type ContainerInfo struct {
	ID     string
	Labels map[string]string
}

ContainerInfo is a summary of a running container used during process recovery.

type DockerRuntime

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

DockerRuntime implements Runtime using the Docker Engine API. Each app runs in its own container with the bundle directory mounted at /app.

func NewDockerRuntime

func NewDockerRuntime(socketPath, pythonImage, rImage, networkMode string) (*DockerRuntime, error)

NewDockerRuntime creates a DockerRuntime connected to socketPath. networkMode must be "bridge" or "host" (validated by config). Returns an error if the socket is unreachable (verified by pinging the API).

func (*DockerRuntime) AppBindHost added in v0.2.2

func (r *DockerRuntime) AppBindHost() string

AppBindHost returns the address the app should bind inside the container. In host-network mode the container shares the host loopback, so 127.0.0.1 keeps the "only the proxy can reach the app" boundary intact. In bridge mode the container has its own network namespace; the listener must bind 0.0.0.0 inside the container so the published 127.0.0.1:port mapping on the host can route to it.

func (*DockerRuntime) HostPreparesDeps added in v0.2.2

func (r *DockerRuntime) HostPreparesDeps() bool

HostPreparesDeps reports false: dependency installation happens inside the container (via uv/Rscript present in the base image), so callers must not run uv sync / renv::restore on the host.

func (*DockerRuntime) HostProvidesAppData added in v0.6.1

func (r *DockerRuntime) HostProvidesAppData() bool

HostProvidesAppData reports that the local Docker runtime provisions app data on the control-plane host and mounts it into the container.

func (*DockerRuntime) InspectPID

func (r *DockerRuntime) InspectPID(containerID string) (int, error)

InspectPID returns the host PID of the container's init process.

func (*DockerRuntime) ListByLabel

func (r *DockerRuntime) ListByLabel(labelFilter string) ([]ContainerInfo, error)

ListByLabel returns containers with the given label filter (JSON filter string).

func (*DockerRuntime) PublishedHostPort added in v0.6.1

func (r *DockerRuntime) PublishedHostPort(containerID string) (int, error)

PublishedHostPort returns the host port the container's published bind port maps to, or 0 when nothing is published. The data-plane proxy uses this to rebuild its target after an agent restart re-adopts a running container.

func (*DockerRuntime) RemoveContainer added in v0.8.13

func (r *DockerRuntime) RemoveContainer(id string) error

RemoveContainer force-removes a container by ID. force=true handles a paused container, so recovery can reap an orphaned frozen warm container. Satisfies the lifecycle.ContainerLister capability. An empty ID is a no-op.

func (*DockerRuntime) RemoveHandle added in v0.5.1

func (r *DockerRuntime) RemoveHandle(handle RunHandle) error

RemoveHandle force-removes the container behind handle. Long-running app containers are created without AutoRemove (so a crash leaves the container inspectable for recovery), so they must be explicitly removed once the Manager has confirmed the process exited on stop/replace; otherwise stopped containers accumulate. Satisfies the optional containerRemover capability the Manager type-asserts for. A nil/empty ID or an already-gone container is treated as success.

func (*DockerRuntime) Resume added in v0.8.12

func (r *DockerRuntime) Resume(_ context.Context, handle RunHandle) (ReplicaEndpoint, error)

Resume thaws a paused container. It is idempotent: a running, non-paused container returns immediately. pause/unpause leaves the route URL unchanged, so the returned endpoint carries an empty URL and the Manager preserves the known route.

func (*DockerRuntime) RunOnce added in v0.2.1

func (r *DockerRuntime) RunOnce(ctx context.Context, p StartParams, logWriter io.Writer) (ExitInfo, error)

RunOnce creates a one-shot container with AutoRemove=true, starts it, and blocks on /containers/{id}/wait. Ctx cancel sends SIGTERM via the kill API, then SIGKILL after a 10-second grace.

func (*DockerRuntime) SetSnapshot added in v0.8.12

func (r *DockerRuntime) SetSnapshot(enabled bool, reclaimMinFraction float64)

SetSnapshot enables warm-wake (freeze + cgroup reclaim) and sets the reclaim-success threshold. Called once at startup from buildRuntime.

func (*DockerRuntime) Signal

func (r *DockerRuntime) Signal(handle RunHandle, sig syscall.Signal) error

func (*DockerRuntime) Start

func (r *DockerRuntime) Start(_ context.Context, p StartParams, logWriter io.Writer) (ReplicaEndpoint, error)

func (*DockerRuntime) Stats

func (r *DockerRuntime) Stats(ctx context.Context, handle RunHandle) (float64, uint64, error)

func (*DockerRuntime) Suspend added in v0.8.12

func (r *DockerRuntime) Suspend(_ context.Context, handle RunHandle) (bool, error)

Suspend freezes the container (docker pause) and reclaims its resident memory to swap via cgroup v2 memory.reclaim, returning freed=true only when the reclaimed fraction meets the configured threshold. On any non-(true,nil) result it unpauses so the caller's Stop path operates on a normal container. When snapshot is disabled it reports ErrRuntimeNotSnapshotter so the watcher hibernates via Stop.

func (*DockerRuntime) Wait

func (r *DockerRuntime) Wait(ctx context.Context, handle RunHandle) error

type DurableDataReporter added in v0.9.5

type DurableDataReporter interface {
	// TierHasDurableData reports whether app-data on this tier survives task
	// restart/hibernation and is shared across replicas.
	TierHasDurableData() bool
}

DurableDataReporter is an optional capability for runtimes whose per-app data dir may NOT survive a restart or be shared across replicas. A runtime that does not implement it is treated as durable (native, docker, and remote workers all back the data dir with a persistent host directory). Only the Fargate runtime implements it, returning false unless a durable backend (S3 Files, or an operator-asserted volume) is configured. The durable-data guard consumes it via Manager.TierHasDurableDataFor to block deploying a data-using app onto a tier that would silently lose its data.

type EnvResolver

type EnvResolver func(slug string) (env []string, secretEnv []string, err error)

EnvResolver returns the per-app environment for the given slug as two "KEY=VALUE" slices: env holds non-secret values, secretEnv holds decrypted secret values. They are kept separate so a runtime can deliver secrets out of band (e.g. the Fargate task definition's secrets block) instead of as plaintext. It is called during Start to inject per-app env before launch.

type ExitInfo added in v0.2.1

type ExitInfo struct {
	Code     int  // exit code; -1 if Signaled
	Signaled bool // true if killed by signal (e.g. SIGKILL after timeout)
}

ExitInfo summarizes how a one-shot process ended.

type ExitVerdict added in v0.9.0

type ExitVerdict struct {
	OOMKilled bool
	// MemoryLimitMB is the enforced per-replica limit at the time of the exit
	// (0 when unknown, e.g. an adopted process). The watcher falls back to the
	// app's stored limit when this is 0.
	MemoryLimitMB int
	At            time.Time
}

ExitVerdict records how a replica's most recent process exited. It survives the entry being replaced on restart so the watcher can still name an OOM-kill after the crash-restart budget is spent.

type GopsutilSampler

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

GopsutilSampler is the production Sampler that reads from the OS via gopsutil. It caches process handles by PID so gopsutil can compute CPU deltas across successive Sample calls (gopsutil needs two measurements per *Process instance).

func (*GopsutilSampler) Purge added in v0.8.14

func (g *GopsutilSampler) Purge(alive map[int32]struct{})

Purge evicts cached process handles for PIDs not present in alive. The GopsutilSampler caches a *gops.Process per PID so CPU% can be computed as a delta across calls, and it only drops an entry when a Sample for that PID fails. A long-running caller that samples only currently-running PIDs (the metrics-history collector) never re-samples an exited PID, so without periodic pruning the cache grows unbounded as PIDs churn. Callers pass the set of live PIDs each cycle; everything else is dropped.

func (*GopsutilSampler) Sample

func (g *GopsutilSampler) Sample(handle RunHandle) (Stats, error)

Sample returns CPU% and RSS for the process identified by handle.PID. The first call for a PID always returns CPUPercent = 0.0 because gopsutil needs two measurements to compute a delta. Subsequent calls return the real value.

type InventoryItem added in v0.6.1

type InventoryItem struct {
	ContainerID string
	Labels      map[string]string
	// Running is true for any task not in STOPPED state (PROVISIONING, PENDING,
	// or RUNNING). It is false only when the task has terminated. Consumers
	// that need a routable URL must additionally check URL != "".
	Running  bool
	URL      string
	WorkerID string
}

InventoryItem describes one managed container as reported by a remote runtime's inventory. Recovery reconciles a replica row against these items by matching the slug/replica_index/deployment_id labels, then routes to URL. WorkerID names the worker that reported the container; with inventory aggregated across a tier's coexisting workers, recovery uses it to bind a replica row to its owning worker's container, so a same-labeled container on another worker is not adopted with the wrong worker's URL, handle, and transport.

Running means "not stopped": for the Fargate runtime a task in PROVISIONING, PENDING, or RUNNING state is reported as Running=true. Only STOPPED tasks are Running=false. This is intentional: a Fargate task that has not yet acquired an IP is not yet routable, but it is NOT gone and must not trigger re-placement. Consumers that need "routable now" must check URL != "" in addition to Running.

type LogFile

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

LogFile is a size-capped, append-only log destination for one app process. It implements io.WriteCloser and is safe for concurrent writes from the stdout and stderr goroutines that the OS spawns when cmd.Stdout and cmd.Stderr are both set to the same writer.

func OpenLogFile

func OpenLogFile(path string, maxSize int64) (*LogFile, error)

OpenLogFile opens or creates the log file at path for appending.

func (*LogFile) Close

func (l *LogFile) Close() error

Close flushes and closes the underlying file.

func (*LogFile) Write

func (l *LogFile) Write(p []byte) (int, error)

Write implements io.Writer. Rotates when the size cap would be exceeded.

type LogReader

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

LogReader reads from an app log file on disk. Its Tail and Follow methods open independent read handles so they work regardless of whether the write side (LogFile) is open or closed.

func NewLogReader

func NewLogReader(path string) *LogReader

NewLogReader creates a reader for the log file at path.

func (*LogReader) Follow

func (r *LogReader) Follow(ctx context.Context, lines chan<- string)

Follow sends new lines written to the log file to lines until ctx is cancelled. It polls the file at 100 ms intervals.

func (*LogReader) Tail

func (r *LogReader) Tail(n int) ([]string, error)

Tail returns the last n lines from the log file in chronological order. It reads backward from the end in chunks, so the work is proportional to the size of the returned tail rather than the whole (up to multi-MB) file - the hot path for the log viewer and every new SSE follow connection.

type Manager

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

Manager tracks running app processes as a pool of replicas per slug. entries maps slug → slice indexed by replica index; nil means that slot is down.

func NewManager

func NewManager(appsDir string, rt Runtime) *Manager

NewManager returns an initialized Manager using the given Runtime as the default ("local") tier. Additional tiers are added via RegisterRuntime.

func (*Manager) Adopt

func (m *Manager) Adopt(slug string, info ProcessInfo, handle RunHandle)

Adopt re-registers a process that was not started by this Manager instance (e.g. recovered after a server restart). It starts the exit-monitoring goroutine so crashed processes are detected normally.

func (*Manager) All

func (m *Manager) All() []*ProcessInfo

All returns a snapshot of all tracked ProcessInfo entries across all slugs.

func (*Manager) AllForSlug added in v0.2.1

func (m *Manager) AllForSlug(slug string) []*ProcessInfo

AllForSlug returns per-replica info for one slug, preserving index order. Slots for down replicas are nil.

func (*Manager) AppBindHostFor added in v0.6.1

func (m *Manager) AppBindHostFor(tier string) string

AppBindHostFor proxies to the runtime registered for the named tier so deploy code can construct the per-replica command with the right listen address. An empty or unregistered tier falls back to the default tier. See Runtime.AppBindHost for the contract.

func (*Manager) AutoInstrumentAppsDefault added in v0.8.5

func (m *Manager) AutoInstrumentAppsDefault() bool

AutoInstrumentAppsDefault reports the fleet-wide auto-instrumentation default. Per-app shinyhub.toml [tracing] auto overrides it at boot time.

func (*Manager) EvictReplicaIfWorker added in v0.6.2

func (m *Manager) EvictReplicaIfWorker(slug string, index int, workerID string)

EvictReplicaIfWorker removes a replica from the manager's view without signaling its runtime, freeing the slug+index slot so a re-placement Start succeeds. It is used when the backing worker is already gone (heartbeat down-sweep or admin revoke): unlike StopReplica it sends no signal, because dialing a dead worker would hang. The evicted entry's log file is closed under the lock; the entry's own (now stale) exit-monitor goroutine sees the slot nil and is a no-op.

Eviction is gated on the entry still being owned by workerID: a worker-loss pass can race a redeploy that already re-placed this slot onto a healthy worker (registering its route and starting a new manager entry before persisting the new replica row). Evicting unconditionally would drop that live replacement; so an entry owned by a different worker is left untouched. A no-op when the slot is already empty.

func (*Manager) ForceEntry

func (m *Manager) ForceEntry(slug string, info ProcessInfo)

ForceEntry directly inserts a ProcessInfo without starting an exit-monitoring goroutine. Used in tests to inject state without starting a real process. For production recovery use Adopt, which starts the monitoring goroutine.

func (*Manager) GetReplica added in v0.2.1

func (m *Manager) GetReplica(slug string, index int) (*ProcessInfo, bool)

GetReplica returns a snapshot of the ProcessInfo for a specific replica.

func (*Manager) HandleReplica added in v0.2.1

func (m *Manager) HandleReplica(slug string, index int) (RunHandle, bool)

HandleReplica returns the RunHandle for a specific replica, or false if not tracked.

func (*Manager) HostPreparesDepsFor added in v0.6.1

func (m *Manager) HostPreparesDepsFor(tier string) bool

HostPreparesDepsFor proxies to the runtime registered for the named tier so deploy code can ask whether host-side dependency installation (uv sync, renv::restore) is expected before Start. An empty or unregistered tier falls back to the default tier. See Runtime.HostPreparesDeps for the contract.

func (*Manager) LastExit added in v0.9.0

func (m *Manager) LastExit(slug string, index int) (ExitVerdict, bool)

LastExit returns the most recent exit verdict for a replica (whether it was OOM-killed and the limit in force), or ok=false when none is recorded.

func (*Manager) LogReader

func (m *Manager) LogReader(slug string, index int) (*LogReader, bool)

LogReader returns a LogReader for a specific replica's log file. Returns false if no log file exists yet (replica has never been started).

func (*Manager) LogTail added in v0.8.20

func (m *Manager) LogTail(slug string, index, n int) string

LogTail returns the last n lines of a replica's log file joined by newlines, or "" when the log cannot be read. Used to capture a crash diagnostic (e.g. a Python traceback) when an app transitions to "crashed".

func (*Manager) PlanPlacement added in v0.7.0

func (m *Manager) PlanPlacement(tier, slug string, count int) []string

PlanPlacement asks the tier's runtime to plan worker assignments for count replicas of slug, returning one target worker node id per replica in assignment order. It returns nil when the tier's runtime does not route to workers (native local tier), in which case replicas have no target worker and the runtime places them itself. Deploy calls this once up front so a concurrent pool boot spreads across workers instead of each replica self-placing against the same pre-deploy load snapshot.

func (*Manager) RegisterRuntime added in v0.6.1

func (m *Manager) RegisterRuntime(tier string, rt Runtime)

RegisterRuntime adds or replaces the runtime for the named tier. Safe to call concurrently with RuntimeForTier lookups.

func (*Manager) ResolveAppEnv added in v0.10.4

func (m *Manager) ResolveAppEnv(slug string) ([]string, error)

ResolveAppEnv returns the app's stored per-app environment - non-secret and decrypted secret values combined into one KEY=VALUE slice - via the configured env resolver. It exists for app-controlled code paths that run outside Start (host-side dependency builds and post-deploy hooks) and have no out-of-band secret channel. Nil-safe: a nil Manager or unset resolver returns (nil, nil). A resolver error propagates so callers fail closed, matching Start.

func (*Manager) ResourceEnforcement added in v0.9.0

func (m *Manager) ResourceEnforcement(tiers ...string) (memory, cpu bool)

ResourceEnforcement reports whether per-app memory/CPU limits are actually enforced across the given tiers (an app may span several). A limit is reported enforced only when enforced on EVERY tier (AND), so an app that runs partly on a native host without cgroup delegation is correctly flagged. The native runtime is best-effort (gated on cgroup v2 delegation); container/remote runtimes apply hard limits, so a runtime that does not implement ResourceEnforcer is treated as enforcing both. With no tiers, the default tier is used.

func (*Manager) Resume added in v0.8.12

func (m *Manager) Resume(slug string, index int) (ReplicaEndpoint, error)

Resume restores a single suspended replica via the tier runtime's Snapshotter capability and returns its (possibly updated) route endpoint. The in-memory entry returns to StatusRunning with the resumed endpoint's URL/WorkerID/handle. Returns a wrapped ErrRuntimeNotSnapshotter, ErrReplicaNotSuspended, or ErrReplicaNotFound sentinel when the slot cannot be resumed, so the caller cold-boots it instead.

func (*Manager) RunningContainerIDs added in v0.5.1

func (m *Manager) RunningContainerIDs() map[string]bool

RunningContainerIDs returns the set of container IDs the Manager currently has adopted across all slugs. Empty for native runtime (handles carry a PID, not a container ID). Used by the startup orphan-container sweep to decide which ShinyHub-labeled containers have no live owner.

func (*Manager) RuntimeForTier added in v0.6.1

func (m *Manager) RuntimeForTier(tier string) Runtime

RuntimeForTier returns the runtime backing the named tier, falling back to the default tier when tier is empty or unregistered. Exposed for recovery, which routes each replica's re-adoption to its tier's runtime (so one app's replicas can span a native default tier and a container-backed burst tier).

func (*Manager) SetAppDataRoot added in v0.2.1

func (m *Manager) SetAppDataRoot(root string) error

SetAppDataRoot sets the root directory under which per-app persistent data directories live. Each Start resolves <root>/<slug>, ensures it exists, stamps it onto StartParams.AppDataPath, and symlinks <bundle_dir>/data to it. Injection of SHINYHUB_APP_DATA into the child env is the Runtime's responsibility (NativeRuntime uses the host path; DockerRuntime translates to the in-container mount path) — the Manager only owns the dir + symlink. An empty root disables the feature. Must be called before the manager begins starting processes; not safe to call concurrently with Start.

func (*Manager) SetAutoInstrumentAppsDefault added in v0.8.5

func (m *Manager) SetAutoInstrumentAppsDefault(v bool)

SetAutoInstrumentAppsDefault sets the fleet-wide default for launching Python apps under opentelemetry-instrument. Wired once at startup from tracing.auto_instrument_apps, before any deploys run, like the platform default env resolver. Must be called before Start; not safe to call concurrently with boots.

func (*Manager) SetDefaultTier added in v0.6.1

func (m *Manager) SetDefaultTier(name string)

SetDefaultTier renames the default tier and rekeys the seed runtime under that name. NewManager registers the seed runtime under DefaultTier ("local"); when the config's first tier is named differently, call this once at startup so empty/unknown tiers still resolve to the seed runtime. A no-op when name is empty or already the default. Must be called before the manager begins starting processes; it is not safe to call concurrently with Start.

func (*Manager) SetEnvResolver

func (m *Manager) SetEnvResolver(r EnvResolver)

SetEnvResolver sets the function used to inject per-app environment variables during Start. Must be called before the manager begins starting processes; it is not safe to call concurrently with Start.

func (*Manager) SetPlatformDefaultEnvResolver added in v0.4.1

func (m *Manager) SetPlatformDefaultEnvResolver(r PlatformDefaultEnvResolver)

SetPlatformDefaultEnvResolver sets the function that supplies platform-wide default env vars (currently OTEL_* tracing config). The returned values are prepended to the env so user-supplied per-app env wins on duplicate keys. Must be called before Start; not safe to call concurrently with Start.

func (*Manager) SetSharedMountResolver added in v0.2.1

func (m *Manager) SetSharedMountResolver(r SharedMountResolver)

SetSharedMountResolver sets the function used to resolve shared mounts during Start. Must be called before the manager begins starting processes; not safe to call concurrently with Start.

func (*Manager) SetStopGrace added in v0.8.26

func (m *Manager) SetStopGrace(d time.Duration)

SetStopGrace sets how long StopReplica waits after SIGTERM before escalating to SIGKILL. Must be called before the manager begins stopping processes; it is not safe to call concurrently with StopReplica.

func (*Manager) Start

func (m *Manager) Start(p StartParams) (*ProcessInfo, error)

Start spawns a new process for the given slug and replica index. Returns an error if that replica is already running.

func (*Manager) Status

func (m *Manager) Status(slug string) (*ProcessInfo, error)

Status returns the first running replica, or a synthetic stopped record. Callers that need per-replica info should use AllForSlug.

func (*Manager) Stop

func (m *Manager) Stop(slug string) error

Stop signals all replicas for a slug to stop in parallel and waits for all to exit.

func (*Manager) StopAll added in v0.5.1

func (m *Manager) StopAll() error

StopAll gracefully stops every tracked app across all slugs, concurrently. Used on server shutdown when server.shutdown_apps is "stop" so the host is left clean instead of with orphaned subprocesses/containers. Errors are aggregated; a failure to stop one app does not block the others.

func (*Manager) StopReplica added in v0.2.1

func (m *Manager) StopReplica(slug string, index int) error

StopReplica signals a single replica to stop and waits for it to exit. If the process does not exit within the stop grace (default defaultStopGrace, configurable via SetStopGrace), SIGKILL is sent.

func (*Manager) Suspend added in v0.8.12

func (m *Manager) Suspend(slug string) (bool, error)

Suspend freezes every running replica of slug via the tier runtime's Snapshotter capability, releasing host RAM. It returns freed=true ONLY when every replica's warmed memory was released; in that case each frozen replica's in-memory status becomes StatusSuspended (the entry is kept - the process/ container is paused, not gone). If the runtime is not a Snapshotter, or any replica could not be freed, Suspend restores any replicas it had frozen (so the whole pool is back to a normal running state the caller can Stop) and returns freed=false, so the caller falls back to Stop (which always frees RAM).

func (*Manager) SuspendReplica added in v0.8.13

func (m *Manager) SuspendReplica(slug string, index int) (bool, error)

SuspendReplica freezes a single running replica via the tier runtime's Snapshotter, releasing its host RAM, and flips the in-memory entry to StatusSuspended on success. It is the per-replica analogue of Suspend, mirroring Resume's index-addressed shape, used by the warm pool to freeze drained replicas while the floor keeps serving.

Returns freed=true ONLY when the warmed memory was actually released. On any other result the driver has already restored the replica to a normally stoppable state (per the Snapshotter contract), so the entry is left StatusRunning and the caller falls back to StopReplica: (false, ErrRuntimeNotSnapshotter) when the tier runtime cannot snapshot, (false, nil) when too little was reclaimed, (false, err) on a driver error.

func (*Manager) TierHasDurableDataFor added in v0.9.5

func (m *Manager) TierHasDurableDataFor(tier string) bool

TierHasDurableDataFor reports whether app-data on the named tier survives task restart/hibernation and is shared across replicas. A runtime that does not implement DurableDataReporter is treated as durable (native/docker/remote all back the data dir with a persistent host directory); only Fargate reports ephemeral storage when no durable backend is configured. An empty or unregistered tier falls back to the default tier. The durable-data guard uses this to block deploying a data-using app onto a tier that would lose its data.

func (*Manager) TransportForWorker added in v0.7.0

func (m *Manager) TransportForWorker(tier, nodeID string) http.RoundTripper

TransportForWorker returns the HTTP transport a tier's runtime requires for reaching replicas hosted by the named worker, or nil to use the default transport. Runtimes opt in by implementing ReplicaTransporter; routes are keyed per-worker so a replica is always dialed with its host worker's transport even when several workers serve the tier.

type NativeRuntime

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

NativeRuntime runs app processes as direct OS child processes.

func NewNativeRuntime

func NewNativeRuntime() *NativeRuntime

NewNativeRuntime returns a ready-to-use NativeRuntime.

func (*NativeRuntime) AppBindHost added in v0.2.2

func (r *NativeRuntime) AppBindHost() string

AppBindHost returns "127.0.0.1": native processes share the host network and must only be reachable via the in-process proxy.

func (*NativeRuntime) ConsumeOOMKill added in v0.9.0

func (r *NativeRuntime) ConsumeOOMKill(pid int) bool

ConsumeOOMKill reports whether the pid's last exit was a kernel OOM-kill and clears the per-pid OOM bookkeeping. Implements the manager's oomReporter.

func (*NativeRuntime) HostPreparesDeps added in v0.2.2

func (r *NativeRuntime) HostPreparesDeps() bool

HostPreparesDeps reports true: native runtime executes app processes on the host using its PATH, so bundle dependencies must be installed locally before Start.

func (*NativeRuntime) HostProvidesAppData added in v0.6.1

func (r *NativeRuntime) HostProvidesAppData() bool

HostProvidesAppData reports that the native runtime provisions app data on the local host.

func (*NativeRuntime) ReadoptCgroup added in v0.9.0

func (r *NativeRuntime) ReadoptCgroup(slug string, index, pid int) error

ReadoptCgroup re-registers an adopted replica's per-app cgroup independent of warm-wake, so a limited replica adopted after a server restart can still be torn down and have its OOM-kills detected. Best-effort and idempotent: it no-ops when the delegated base is unavailable or the deterministic cgroup either does not exist or no longer holds the pid (i.e. the replica was uncapped). It re-seeds the OOM baseline so only post-readopt kills count.

func (*NativeRuntime) ReadoptWarm added in v0.8.17

func (r *NativeRuntime) ReadoptWarm(slug string, index, pid int) error

ReadoptWarm re-registers the per-app cgroup of a replica adopted from a prior process life (after a server restart), so warm-wake (Suspend/Resume) works for it again. The cgroup survives the restart on disk; only this runtime's in-memory appCgroups mapping is lost, and Adopt - unlike Start - never rebuilds it. ReadoptWarm reconstructs the deterministic app-<slug>-<index> directory under the delegated base, confirms the adopted PID is still a member, and re-registers the mapping. It is the adoption-time analogue of placeInAppCgroup.

Best-effort: ErrRuntimeNotSnapshotter when warm-wake is off or its base never came up (the caller stays silent; the replica hibernates via Stop as today); any other error means the cgroup is gone or no longer holds the PID (the caller logs and degrades).

func (*NativeRuntime) ResourceEnforcement added in v0.9.0

func (r *NativeRuntime) ResourceEnforcement() (memory, cpu bool)

ResourceEnforcement reports whether per-app memory / cpu limits are actually enforced on this host (the controller is delegated to the service), preparing the cgroup base on first call. The UI/API surface this so an operator is not misled into thinking a limit applies when cgroup delegation is absent.

func (*NativeRuntime) Resume added in v0.8.12

func (r *NativeRuntime) Resume(_ context.Context, handle RunHandle) (ReplicaEndpoint, error)

Resume thaws a previously suspended replica (SIGCONT). It is idempotent: SIGCONT on an already-running process group is a no-op. The PID and port are preserved, so the route URL is unchanged and the returned endpoint carries an empty URL for the Manager to preserve the known route. A vanished process group (ESRCH) is a genuine error so the caller cold-boots; its now-empty cgroup is reclaimed by the replica's Wait.

func (*NativeRuntime) RunOnce added in v0.2.1

func (r *NativeRuntime) RunOnce(ctx context.Context, p StartParams, logWriter io.Writer) (ExitInfo, error)

RunOnce blocks until the process exits or ctx is cancelled. On ctx cancel, the process group receives SIGTERM, then SIGKILL after a 10-second grace.

func (*NativeRuntime) SetIsolation added in v0.9.1

func (r *NativeRuntime) SetIsolation(level sandbox.Level)

SetIsolation sets the native process-isolation dial. Called once at startup from buildRuntime, before any Start. If isolation is requested on a platform with no enforcement backend (non-Linux), it warns and runs without it rather than failing to start apps.

func (*NativeRuntime) SetSnapshot added in v0.8.12

func (r *NativeRuntime) SetSnapshot(enabled bool, reclaimMinFraction float64)

SetSnapshot enables warm-wake (SIGSTOP freeze + per-app cgroup reclaim) and sets the reclaim-success threshold. Called once at startup from buildRuntime, before any Start. The delegated cgroup base is prepared lazily on the first Start that needs it (see ensureCgroupBase); if that preparation fails the runtime degrades gracefully and hibernates via Stop as before.

func (*NativeRuntime) Signal

func (r *NativeRuntime) Signal(handle RunHandle, sig syscall.Signal) error

func (*NativeRuntime) Start

func (r *NativeRuntime) Start(_ context.Context, p StartParams, logWriter io.Writer) (ReplicaEndpoint, error)

func (*NativeRuntime) Stats

func (r *NativeRuntime) Stats(_ context.Context, handle RunHandle) (float64, uint64, error)

func (*NativeRuntime) Suspend added in v0.8.12

func (r *NativeRuntime) Suspend(_ context.Context, handle RunHandle) (bool, error)

Suspend freezes a replica's process group (SIGSTOP) and reclaims its resident memory to swap via its per-app cgroup's memory.reclaim, returning freed=true only when the reclaimed fraction meets the configured threshold. On any non-(true,nil) result it sends SIGCONT so the process is left normally stoppable. When warm-wake is disabled or its cgroup base never came up it reports ErrRuntimeNotSnapshotter so the watcher hibernates via Stop; a replica that was started without a per-app cgroup reports (false, nil) for the same fallback without flagging the whole runtime as non-snapshotting.

func (*NativeRuntime) Wait

func (r *NativeRuntime) Wait(ctx context.Context, handle RunHandle) error

type PartialInventoryError added in v0.7.0

type PartialInventoryError struct {
	Workers []string
}

PartialInventoryError reports that a tier's aggregated inventory is incomplete: at least one worker was queried successfully, but Workers could not be reached. The returned items hold what the reachable workers reported. Recovery uses Workers to distinguish a replica whose container is genuinely gone (its owning worker reported and the container was absent) from one whose owning worker was merely unreachable (status unknown); the latter must not drive a live app to stopped.

func (*PartialInventoryError) Error added in v0.7.0

func (e *PartialInventoryError) Error() string

type PlatformDefaultEnvResolver added in v0.4.1

type PlatformDefaultEnvResolver func(slug string, replica int) []string

PlatformDefaultEnvResolver returns "KEY=VALUE" platform defaults that should be set BEFORE the user's per-app env, so user values win on duplicate keys. This is the slot for OTEL_* env vars that the operator configures platform-wide but each app may still override per-app via the env-var UI. Returning nil disables the hook.

type ProcessInfo

type ProcessInfo struct {
	Slug         string
	Index        int
	PID          int
	Port         int
	Status       Status
	Tier         string
	Provider     string
	EndpointURL  string
	WorkerID     string
	AppVersion   string
	DeploymentID int64
	// OOMKilled is set when this replica's most recent exit was a kernel
	// OOM-kill (it exceeded its memory limit). Used to surface a crash reason
	// that names the limit rather than a generic crash.
	OOMKilled bool
}

type ReplicaEndpoint added in v0.6.1

type ReplicaEndpoint struct {
	URL      string    // route URL, e.g. "http://127.0.0.1:34521"
	Provider string    // "native" | "docker" (future: "remote_docker" | "fargate")
	WorkerID string    // stable identity: PID (stringified), container ID, task ARN
	Handle   RunHandle // operational handle
}

ReplicaEndpoint is the result of starting a replica: where the proxy routes to it, which provider owns it, a stable worker identity used for recovery, and the operational RunHandle for Signal/Wait/Stats/removal. A remote runtime returns a non-loopback URL here; local runtimes return http://127.0.0.1:<port>.

type ReplicaInventory added in v0.6.1

type ReplicaInventory interface {
	Inventory(ctx context.Context) ([]InventoryItem, error)
}

ReplicaInventory is an optional capability for runtimes that can enumerate their live replicas without a host PID (remote workers). RecoverProcesses uses it to reconcile remote tiers by deployment id instead of InspectPID.

type ReplicaPlacer added in v0.7.0

type ReplicaPlacer interface {
	PlanPlacement(slug string, count int) []string
}

ReplicaPlacer is the optional capability a worker-routing Runtime implements to plan where a batch of replicas should land. PlanPlacement returns one target worker node id per replica, in assignment order, spreading the batch across the tier's workers. Runtimes that do not route to workers (the native local tier) do not implement it.

type ReplicaTransporter added in v0.6.1

type ReplicaTransporter interface {
	// ReplicaTransportForWorker returns the RoundTripper to use when dialing
	// replicas hosted by the named worker, or nil to use the default transport
	// (also returned when the worker is not a live host on this runtime's tier).
	ReplicaTransportForWorker(nodeID string) http.RoundTripper
}

ReplicaTransporter is an optional capability for runtimes that route replica traffic through a non-default HTTP transport (for example a remote worker's mTLS tunnel). The proxy and health-check paths use this transport so that requests to the replica's reported URL authenticate correctly. The transport is per-worker: a tier may have several workers, and each replica's route must use the mTLS transport of the worker that actually hosts it.

type ResourceEnforcer added in v0.9.0

type ResourceEnforcer interface {
	// ResourceEnforcement reports whether memory and cpu limits are enforced.
	ResourceEnforcement() (memory, cpu bool)
}

ResourceEnforcer is the optional runtime capability to report whether per-app memory/CPU limits are ACTUALLY enforced on this host. Only the native runtime implements it (enforcement is best-effort, gated on cgroup v2 delegation); container/remote runtimes always enforce, so they do not implement it and the manager treats them as enforcing.

type RunHandle

type RunHandle struct {
	PID         int    // set by NativeRuntime
	ContainerID string // set by DockerRuntime
}

RunHandle identifies a running app instance. Exactly one field is non-zero depending on the runtime in use.

type Runtime

type Runtime interface {
	// Start spawns a new process. logWriter receives combined stdout+stderr.
	// The returned ReplicaEndpoint carries the route URL the proxy must use,
	// the provider name, a durable worker identity, and the operational handle.
	Start(ctx context.Context, p StartParams, logWriter io.Writer) (ReplicaEndpoint, error)
	// Signal sends sig to the process or container identified by handle.
	Signal(handle RunHandle, sig syscall.Signal) error
	// Wait blocks until the process or container identified by handle exits.
	Wait(ctx context.Context, handle RunHandle) error
	// Stats returns CPU usage (percent, 0–100+) and RSS bytes for the handle.
	Stats(ctx context.Context, handle RunHandle) (cpuPercent float64, rssBytes uint64, err error)
	// RunOnce spawns a short-lived process from the same bundle/runtime context
	// as Start, blocks until it exits or ctx is cancelled, and returns the
	// exit info. Implementations MUST signal SIGTERM on ctx cancel and
	// SIGKILL after a 10-second grace.
	RunOnce(ctx context.Context, p StartParams, logWriter io.Writer) (ExitInfo, error)
	// HostPreparesDeps reports whether bundle dependencies (uv sync,
	// renv::restore) should be installed on the host before Start. Native
	// runtimes use the host's PATH and need this; container runtimes prepare
	// deps inside the image/container, so callers must NOT touch the host.
	HostPreparesDeps() bool
	// AppBindHost reports the address an app process should bind its listening
	// socket to. Native and Docker host-network runtimes return "127.0.0.1" so
	// only the in-process proxy can reach the app. Docker bridge-network
	// runtimes return "0.0.0.0" so the published port mapping (which lives in
	// the container's separate network namespace) is reachable from the host.
	AppBindHost() string
	// HostProvidesAppData reports whether the host running this Manager is
	// responsible for provisioning the per-app data directory and shared-mount
	// host paths. Local runtimes (native, docker on the control-plane host)
	// return true. Remote runtimes return false: the worker provisions its own
	// app-data, so the Manager must not create host directories or symlinks and
	// must strip host paths before dispatching Start.
	HostProvidesAppData() bool
}

Runtime abstracts how app processes are started and managed. NativeRuntime uses exec.Command; DockerRuntime uses the Docker Engine API.

type RuntimeSampler

type RuntimeSampler struct {
	Runtime Runtime
}

RuntimeSampler implements Sampler by delegating to Runtime.Stats. Used when DockerRuntime is active so stats are fetched via the Docker API.

func (*RuntimeSampler) Sample

func (r *RuntimeSampler) Sample(handle RunHandle) (Stats, error)

type Sampler

type Sampler interface {
	Sample(handle RunHandle) (Stats, error)
}

Sampler reads CPU and memory stats for a running app process.

type SharedMount added in v0.2.1

type SharedMount struct {
	SourceSlug string // for path naming under data/shared/<source-slug>
	HostPath   string // absolute path on the host (the source app's app-data dir)
}

SharedMount is a read-only mount of another app's data dir into the consumer.

type SharedMountResolver added in v0.2.1

type SharedMountResolver func(slug string) ([]SharedMount, error)

SharedMountResolver returns the shared mounts for a slug. Empty slice means no mounts. Called once per Start; failures abort the start.

type Snapshotter added in v0.8.12

type Snapshotter interface {
	// Suspend freezes the replica identified by handle and tries to release its
	// warmed memory from host RAM. It returns freed=true ONLY when the warmed
	// memory was actually released (driver-defined threshold). On any result
	// other than (true, nil) - freed=false OR err != nil, including an error
	// after a partial freeze - the driver MUST first restore the replica to a
	// normally stoppable state so the caller's Stop path works without
	// special-casing a frozen cgroup. The handle stays valid for a later Resume
	// only on (true, nil).
	Suspend(ctx context.Context, handle RunHandle) (freed bool, err error)

	// Resume restores a previously suspended replica and returns its route
	// endpoint (the same URL when the driver preserves the process/port in
	// place). Resume MUST be idempotent: if the replica is already serving,
	// return the current endpoint and nil error. On a genuine error the driver
	// MUST tear down the stale frozen/suspended resource before returning, so the
	// caller's cold-boot fallback cannot collide with or leak it.
	Resume(ctx context.Context, handle RunHandle) (ReplicaEndpoint, error)
}

Snapshotter is an optional capability for runtimes that can freeze a running replica's warmed memory and restore it, skipping a cold restart on wake. A runtime that does not implement it uses the existing stop/cold-start path.

A runtime that implements Snapshotter must also ensure its Stop/Signal path can terminate a SUSPENDED replica - a frozen resource (e.g. a paused cgroup) does not deliver SIGTERM until it is thawed, so the runtime must unfreeze before killing (or kill the frozen resource directly). Until a real Snapshotter runtime is registered, suspended state never arises in production: Manager. Suspend returns ErrRuntimeNotSnapshotter and the watcher falls back to Stop.

type StartParams

type StartParams struct {
	Slug string
	// AppID is the owning app's numeric DB id. It is used to namespace per-app
	// external resources (e.g. Fargate secret store names and task-definition
	// families) so a delete-then-recreate of the same slug never collides.
	// Zero when unknown (paths that do not touch per-app external resources).
	AppID   int64
	Index   int
	Tier    string // runtime tier; empty => DefaultTier
	Dir     string
	Command []string
	Port    int
	// HostPublishPort, when non-zero, is the host port to publish the
	// in-container bind Port to. The control plane allocates Port (baked into
	// the command and PORT env); a remote worker allocates HostPublishPort on
	// its own host. Zero means publish to the same port as Port (local case).
	HostPublishPort int
	Env             []string
	// SecretEnv carries decrypted secret env vars ("KEY=VALUE"), kept in a slice
	// separate from Env. Every runtime currently injects SecretEnv as plaintext
	// alongside Env (the native, Docker, and Fargate runtimes concatenate the
	// two; a key is either secret or not, so the order is immaterial). The slice
	// is kept distinct so the Fargate runtime can later route these values
	// through the task definition's secrets block instead of plaintext task
	// overrides; until then secret values are NOT hidden from ecs:DescribeTasks.
	SecretEnv       []string
	AppDataPath     string        // host path to per-app data dir; empty disables data-dir wiring in runtime
	MemoryLimitMB   int           // 0 = no limit
	CPUQuotaPercent int           // 0 = no limit; 100 = 1 full core
	SharedMounts    []SharedMount // resolved by caller before Start/RunOnce
	AppVersion      string        // app version stamped onto labels/metadata
	DeploymentID    int64         // owning deployment; 0 when unknown
	ContentDigest   string        // bundle content digest; "" when unknown (remote runtime pulls by this)
	// TargetWorker pins this replica to a specific worker node id. Deploy
	// pre-plans a multi-replica pool's worker assignments up front (so a
	// concurrent batch spreads instead of every replica self-placing onto the
	// same least-loaded worker against an identical pre-deploy snapshot) and
	// stamps the chosen worker here. Empty means the runtime self-places against
	// live load, which is correct for a single-replica boot (e.g. a watchdog
	// restart). Runtimes that do not route to workers ignore it.
	TargetWorker string
	// MaxSessions is the per-replica active-connection hard cap enforced at the
	// worker data plane. 0 means no cap. Persisted as a Docker label so re-adoption
	// after an agent restart restores the same limit.
	MaxSessions int
	// JobRunID, when non-zero, marks this as a one-shot scheduled-job run (via
	// RunOnce, not Start). It namespaces the job's own cgroup (job-<slug>-<runID>)
	// so a capped job never shares replica 0's app-<slug>-0 cgroup.
	JobRunID int64
}

type Stats

type Stats struct {
	CPUPercent float64
	RSSBytes   int64
}

Stats holds a point-in-time resource snapshot for one process.

type Status

type Status string
const (
	StatusRunning   Status = "running"
	StatusStopped   Status = "stopped"
	StatusCrashed   Status = "crashed"
	StatusUnknown   Status = "unknown"
	StatusSuspended Status = "suspended"
)

type TaskRef added in v0.7.0

type TaskRef struct {
	ARN string
}

TaskRef identifies one Fargate task returned by a FargateTaskSweeper. It lives in the process package so both fargate.Runtime and lifecycle can use it without an import cycle (fargate imports process; lifecycle imports both fargate and process; placing TaskRef here breaks the fargate->lifecycle direction that would otherwise form a cycle).

type TierAssignment added in v0.6.1

type TierAssignment struct {
	Index int
	Tier  string
}

TierAssignment binds a global replica index to the tier that should run it. Indexes are contiguous and unique across the whole app (a single global index space); the tier is an attribute, never folded into the index.

func ExpandPlacement added in v0.6.1

func ExpandPlacement(placement map[string]int, tierOrder []string, fallbackReplicas int, defaultTier string) ([]TierAssignment, error)

ExpandPlacement turns a per-tier replica-count map into a deterministic, contiguous list of (index, tier) assignments.

When placement is empty, all fallbackReplicas indexes are assigned to defaultTier, reproducing single-tier behavior exactly. When placement is non-empty, tiers are walked in tierOrder and each is allocated the next contiguous block of indexes; tiers with count 0 are skipped. Every tier in placement must appear in tierOrder. The resolved total must be at least 1.

type WarmReadopter added in v0.8.17

type WarmReadopter interface {
	// ReadoptWarm re-registers the warm-wake state for an adopted replica. It
	// returns ErrRuntimeNotSnapshotter when warm-wake is unavailable (the caller
	// stays silent); any other error means the warm state could not be rebuilt
	// (the caller logs and the replica hibernates via Stop).
	ReadoptWarm(slug string, index, pid int) error
}

WarmReadopter is implemented by a runtime whose warm-wake state is held in this process's memory and is therefore lost when a replica is re-adopted after a server restart - the native runtime, whose per-app cgroup mapping Adopt does not rebuild the way Start does. Manager.Adopt calls ReadoptWarm best-effort so a re-adopted replica can be warm-frozen and warm-resumed again. Runtimes whose warm state survives a restart independently (e.g. Docker's daemon-held paused containers) do not implement it.

Jump to

Keyboard shortcuts

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