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 ¶
- func Every(name string, opts EveryOptions, fn func(ctx context.Context) error) serverkit.Worker
- func HasOperators(operators []Operator) bool
- func LeaseNameOf(w serverkit.Worker) string
- func ReadyChecks(nodes ...Node) []serverkit.ReadyCheck
- func Singleton(l Leader, w serverkit.Worker) serverkit.Worker
- func Supervise(ctx context.Context, policy Policy, nodes ...Node) error
- func WrapWorker(name string, w WorkerLifecycle) serverkit.Worker
- type Closer
- type ContextWorkerInstance
- type Drainer
- type EveryOptions
- type Leader
- type Node
- type Operator
- type Policy
- type Readier
- type Runner
- type SingletonWorker
- type Worker
- type WorkerInstance
- type WorkerLifecycle
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Every ¶ added in v1.0.0
Every returns a worker that calls fn on a fixed cadence until it is stopped. Each run is a trace of its own — a root span named "worker.<name>.run" (observe.RunRoot) — so the business methods, queries and calls it makes nest under it instead of each being a one-span trace. A failed run is logged and the cadence continues; a panicking run is recovered, recorded on its span, and logged, so one bad run never stops the loop or the process.
It is the loop a hand-written worker would write, with the tracing and the recovery it would forget.
func HasOperators ¶
HasOperators reports whether the supplied operator list is non-empty. The generated HasOperators(s) delegates here over OperatorList(s).
func LeaseNameOf ¶
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 ¶
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
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
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
Drainer is a component that stops taking new work and finishes what it has in flight by ctx's deadline.
type EveryOptions ¶ added in v1.0.0
type EveryOptions struct {
// Interval is the pause between the end of one run and the start of the
// next. Runs never overlap.
Interval time.Duration
// RunImmediately runs once at start instead of waiting one Interval.
RunImmediately bool
// Logger receives one line per failed or panicking run. nil uses
// slog.Default().
Logger *slog.Logger
}
EveryOptions configures Every. Interval is required.
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
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
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 ¶
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
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
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 ¶
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.
type WorkerLifecycle ¶
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.