Help us shape the future of Go! Take the 2026 Go Developer Survey

lifecyclekit

package
v1.0.14 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 8 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 Every added in v1.0.0

func Every(name string, opts EveryOptions, fn func(ctx context.Context) error) serverkit.Worker

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

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 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

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