Documentation
¶
Overview ¶
Package lifecyclekit owns 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_gen.go 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 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 lifecyclekit.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, so the generated file imports only lifecyclekit.
- Controller / Options / Run — the controller-manager bridge over operatorkit the generated RunOperators delegates to.
- 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.
The operator bridge (Run, Controller, Options) is a thin pass-through to forge/pkg/appkit/operatorkit so projects WITHOUT operators never compile controller-runtime and its Kubernetes dependency tree: the generated import of the operatorkit-backed Run is conditional on the project having operators (the template only references RunOperators' operatorkit path when .Operators is non-empty).
Index ¶
- func HasOperators(operators []Operator) bool
- func LeaseNameOf(w serverkit.Worker) string
- func Run(ctx context.Context, logger *slog.Logger, opts Options, ...) error
- func Singleton(l Leader, w serverkit.Worker) serverkit.Worker
- func WrapWorker(name string, w WorkerLifecycle) serverkit.Worker
- type ContextWorkerInstance
- type Controller
- type Leader
- type Operator
- type Options
- type SingletonWorker
- type Worker
- type WorkerInstance
- type WorkerLifecycle
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 Run ¶
Run creates a controller manager, registers every controller's scheme and setup, and starts the manager. It is the bridge the generated RunOperators delegates to; behaviour (kubeconfig resolution with graceful no-cluster degrade, leader election, scheme / controller registration, health probe) lives in operatorkit.Run. Blocks until ctx is cancelled — the caller runs it in a goroutine.
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 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 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 Controller ¶
type Controller = operatorkit.Controller
Controller is one generated operator row: the CRD scheme installer and the controller's manager hookup. It is a re-export of operatorkit.Controller so the generated RunOperators references a lifecyclekit type.
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 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 Options ¶
type Options = operatorkit.Options
Options carries the per-project controller-manager configuration the generated RunOperators supplies — a re-export of operatorkit.Options.
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.