lifecyclekit

package
v0.1.46 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package lifecyclekit owns process lifecycle: the four single-method component interfaces (Runner, Drainer, Closer, Readier) and Supervise, which starts a list of components in order and stops them in reverse (supervise.go) — plus the uniform worker/operator supervised-component machinery that the generated internal/app/lifecycle_gen.go delegates to.

Status (FORGE_SHAPE_REDESIGN §2 — lib-boundary extraction)

The generated lifecycle file is the SUPERVISED-component surface: the workers and operators serverkit runs over the constructed *Services. Per the "generated files are tables, not programs" rule, all the uniform worker machinery lives HERE and the generated file shrinks to thin per-app DATA:

  • WorkerList(s) returns explicit lifecyclekit.WrapWorker("name", s.Field) rows;
  • OperatorList(s) returns one row per operator;
  • RunOperators(s, ...) is a single delegation to operatorkit.Run with one dumb Controller row per operator.

What lives here:

  • WorkerInstance / ContextWorkerInstance — lifecycle wrappers (Name / Start / Stop, plus the ctx-aware RunContext sibling) that satisfy serverkit.Worker / serverkit.ContextWorker.
  • WrapWorker — the runtime type-switch the generated WorkerList rows call per worker: a worker implementing RunContext gets the ctx-aware wrapper (per-worker cancel-on-shutdown), everything else gets the legacy Start wrapper.
  • WorkerLifecycle — the Start/Stop shape every generated worker exposes.
  • Worker / Operator — re-exports of the serverkit contracts the generated lists return.
  • HasOperators — the predicate over a serverkit.Operator slice.

Adding a worker needs no `forge generate` change to this package — the type-switch detects RunContext at runtime.

No controller-runtime here

The controller-manager bridge is NOT in this package: it is forge/pkg/appkit/operatorkit ([operatorkit.Run], Controller, Options), and only a project that runs operators imports it. lifecyclekit used to re-export those three names, which made every importer of lifecyclekit — including a binary that only wanted Supervise — link sigs.k8s.io/controller-runtime and its Kubernetes dependency tree. deps_test.go pins that this package's import graph reaches neither.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HasOperators

func HasOperators(operators []Operator) bool

HasOperators reports whether the supplied operator list is non-empty. The generated HasOperators(s) delegates here over OperatorList(s).

func LeaseNameOf

func LeaseNameOf(w serverkit.Worker) string

LeaseNameOf reports the lease a worker runs under, or "" when it is not a Singleton. A project test uses it to pin which workers are leased and which run on every replica, so a worker added later must be classified.

func ReadyChecks added in v0.1.46

func ReadyChecks(nodes ...Node) []serverkit.ReadyCheck

ReadyChecks are the /readyz checks for the nodes that report Ready, one per node, named after it — for serverkit.Drainer.ReadinessHandler, which a root's probe route serves.

func Singleton

func Singleton(l Leader, w serverkit.Worker) serverkit.Worker

Singleton wraps a worker so that, across all replicas sharing the Leader's database, at most one runs it at a time. The others stand by and take over when the holder exits, crashes or loses its session.

Use it for a worker that is NOT safe to run concurrently. A worker that already is (a durable JetStream consumer, a SKIP LOCKED queue drain, an idempotent set-based sweep) should NOT be wrapped: it would idle replicas for no benefit and make the lease a single point of throughput.

The wrapped worker must be restartable: its Start (or RunContext) is called again whenever this replica re-acquires the lease after losing it. A worker that returns nil while holding the lease is treated as finished (a worker that disabled itself), not as a reason to re-contend.

The lease is named "worker/<name>". A nil leader returns w unchanged, for a process with no database to lease from.

func Supervise added in v0.1.46

func Supervise(ctx context.Context, policy Policy, nodes ...Node) error

Supervise starts nodes in order, waits until ctx ends or a runner fails, then stops them in reverse order (see the file comment for both sequences). It returns the first runner failure under FailProcess, nil for a stop that ctx began, and an error for an invalid node list — names must be non-empty and unique, and every node must have a lifecycle method.

ctx is the stop signal — a signal.NotifyContext in a main — and nothing more: every Run gets a context of its own that ends only after its node has drained, so a listener keeps serving through the pre-stop pause.

func WrapWorker

func WrapWorker(name string, w WorkerLifecycle) serverkit.Worker

WrapWorker wraps a constructed worker for the serverkit supervisor. It is the single helper the generated WorkerList rows call — a runtime type-switch (chosen over codegen-time AST detection so the generated table stays one dumb call per row and adding RunContext to a worker needs no `forge generate`):

  • the worker implements RunContext(ctx) → *ContextWorkerInstance, which satisfies serverkit.ContextWorker, so the supervisor's `w.(serverkit.ContextWorker)` assertion sees through the wrapper and uses the ctx-aware lifecycle;
  • otherwise → *WorkerInstance, which has no RunContext method, so the supervisor's legacy Start path is untouched.

Types

type Closer added in v0.1.46

type Closer interface {
	Close(ctx context.Context) error
}

Closer is a component that releases resources — a pool, a connection, a client — bounded by ctx.

type ContextWorkerInstance

type ContextWorkerInstance struct {
	WorkerInstance
	// contains filtered or unexported fields
}

ContextWorkerInstance is the ctx-aware sibling of WorkerInstance: it additionally exposes RunContext, so a value of this type satisfies serverkit.ContextWorker and the serverkit supervisor prefers the ctx-aware lifecycle (per-worker cancel-on-shutdown) over legacy Start. It is a SEPARATE type — not an always-present RunContext on WorkerInstance that sometimes delegates to Start — because the supervisor's preference is a type assertion: a universally-present RunContext would make every worker look ctx-aware and silently change legacy workers' lifecycle.

func NewContextWorkerInstance

func NewContextWorkerInstance(name string, runContext, stop func(ctx context.Context) error) *ContextWorkerInstance

NewContextWorkerInstance builds a ContextWorkerInstance from a worker's name and its RunContext/Stop methods. The embedded WorkerInstance's Start delegates to runContext — semantically identical for a ctx-aware worker (both receive the supervisor's per-worker ctx), and the supervisor never takes the Start path when RunContext is present anyway; Start exists only so the wrapper still satisfies the base serverkit.Worker interface.

func (*ContextWorkerInstance) RunContext

func (w *ContextWorkerInstance) RunContext(ctx context.Context) error

RunContext runs the worker's ctx-aware main loop. See serverkit.ContextWorker for the full shutdown contract.

type Drainer added in v0.1.46

type Drainer interface {
	Drain(ctx context.Context) error
}

Drainer is a component that stops taking new work and finishes what it has in flight by ctx's deadline.

type Leader

type Leader interface {
	Run(ctx context.Context, name string, fn func(ctx context.Context) error) error
}

Leader is the one thing Singleton needs from a lease implementation: run fn only while this process holds the named lease, cancel fn's context when the lease is lost, and keep contending until ctx ends. *lease.Locker (forge/pkg/lease) satisfies it.

type Node added in v0.1.46

type Node struct {
	Name  string
	Run   func(ctx context.Context) error
	Drain func(ctx context.Context) error
	Close func(ctx context.Context) error
	Ready func(ctx context.Context) error
}

Node is one supervised component: a name for the logs and whichever of the four lifecycle methods it has. A nil method is a phase the node does not take part in. Build one with Component, WorkerNode, or a literal.

func Component added in v0.1.46

func Component(name string, c any) Node

Component is the Node for a component value: its Run, Drain, Close and Ready methods, for whichever of Runner, Drainer, Closer and Readier it implements. It is how a hand-written main hands its components to Supervise:

lifecyclekit.Component("api", apiListener)

A method with another signature (a Close() error, say) is not one of the four and is not called; wrap it in a Node literal.

func WorkerNode added in v0.1.46

func WorkerNode(w serverkit.Worker) Node

WorkerNode adapts a serverkit.Worker: it Runs RunContext when the worker is a serverkit.ContextWorker and Start otherwise, and Closes with Stop — which the Worker contract already calls after the run context ends.

type Operator

type Operator = serverkit.Operator

Operator is the minimal contract serverkit needs to count supervised operators — a re-export of serverkit.Operator so the generated OperatorList returns a lifecyclekit type.

type Policy added in v0.1.46

type Policy struct {
	// Drainer is the process's readiness gate and shutdown budget. Supervise
	// marks it ready once every node has started; on stop it Begins it —
	// readiness flips to draining, then PreStopDelay — and bounds the whole
	// reverse walk by its ShutdownContext. Serve its ReadinessHandler on a
	// probe route. nil is serverkit's defaults (5s pre-stop, 30s budget).
	Drainer *serverkit.Drainer

	// Logger receives the supervisor's lines; nil is the Drainer's Logger,
	// then slog.Default(). A nil Drainer.Logger is set to it, so the drain
	// phases go to the same place.
	Logger *slog.Logger

	// FailurePolicy governs a runner that returns an error before the stop:
	// serverkit.FailProcess (the default) stops every node and makes
	// Supervise return that error; serverkit.Ignore logs it and keeps
	// running the rest.
	FailurePolicy serverkit.FailurePolicy

	// Flush runs LAST, after every node has stopped and the shutdown's total
	// is recorded, inside what is left of the budget — the telemetry flush,
	// so it exports the shutdown's own spans, metrics and logs. Optional.
	Flush func(ctx context.Context) error
}

Policy is the root's part of a stop: what happens around the nodes.

type Readier added in v0.1.46

type Readier interface {
	Ready(ctx context.Context) error
}

Readier is a component that contributes to the process's readiness: nil when it can take its share of traffic.

type Runner added in v0.1.46

type Runner interface {
	Run(ctx context.Context) error
}

Runner is a long-lived component: Run returns when ctx ends — or, for a component that also Drains, once its drain is done.

type SingletonWorker

type SingletonWorker struct {
	ContextWorkerInstance
	// contains filtered or unexported fields
}

SingletonWorker is what Singleton returns: a ContextWorker that runs its inner worker under a lease. It is exported so a project can assert, without starting anything, which of its workers are leased (see LeaseNameOf).

func (*SingletonWorker) LeaseName

func (w *SingletonWorker) LeaseName() string

LeaseName is the lease this worker contends for.

type Worker

type Worker = serverkit.Worker

Worker is the runtime contract for a long-running background task — a re-export of serverkit.Worker so the generated WorkerList returns a lifecyclekit type and the generated file imports only lifecyclekit.

type WorkerInstance

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

WorkerInstance wraps a worker with its lifecycle methods (Name / Start / Stop). WrapWorker returns one for legacy Start/Stop workers.

func NewWorkerInstance

func NewWorkerInstance(name string, start, stop func(ctx context.Context) error) *WorkerInstance

NewWorkerInstance builds a WorkerInstance row from a worker's name and Start/Stop methods.

func (*WorkerInstance) Name

func (w *WorkerInstance) Name() string

Name returns the worker's identifier.

func (*WorkerInstance) Start

func (w *WorkerInstance) Start(ctx context.Context) error

Start blocks until ctx is cancelled.

func (*WorkerInstance) Stop

func (w *WorkerInstance) Stop(ctx context.Context) error

Stop is called during graceful shutdown.

type WorkerLifecycle

type WorkerLifecycle interface {
	Start(ctx context.Context) error
	Stop(ctx context.Context) error
}

WorkerLifecycle is the Start/Stop surface every generated worker type exposes — the serverkit.Worker shape minus Name, which the generated table supplies from forge.yaml rather than from the worker itself.

Jump to

Keyboard shortcuts

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