lifecycle

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 10 Imported by: 2

Documentation

Overview

Package lifecycle runs a built graph.System: process startup, readiness, and graceful shutdown.

The package exports:

  • Starter and Stopper, the single-method interfaces a dependency's value implements to take part in startup and in shutdown, and Subsystem, which embeds both
  • ReadyStarter, a Starter that implements Ready: the interface Coordinator.Checks infers readiness checks from
  • Monitored, the interface a dependency's value implements to report runtime failures on a channel the Coordinator watches
  • Config, the Coordinator's configuration, with Config.Merge and Config.Finalize, and Env, the environment-variable name Finalize reads, composed by NewEnv
  • Coordinator, which runs one System once, and New, which returns one
  • Coordinator.Exec, which starts the System, runs a function, and shuts down: the one-shot form a CLI command uses
  • Coordinator.Run, which starts the System, serves until its context ends, and shuts down: the long-running form a service uses
  • Coordinator.OnReady, which registers a hook run once the System is ready, and Coordinator.Monitor, which registers a channel whose failure ends the run
  • Coordinator.Ready, whether the System is ready, and Coordinator.Checks, the System's readiness checks
  • ReadinessChecker, the interface a readiness probe consumes
  • Check, a readiness check paired with its probe name
  • Readiness, a graph node's value that reports its Coordinator's readiness to the nodes that depend on it

A Coordinator's life divides into an inert declaration phase and a single blocking call that owns everything after. Nothing is registered for startup or shutdown: the participants are the System's dependencies, and their order is the System's layers. Before the call, Coordinator.OnReady and Coordinator.Monitor declare what becoming ready and runtime failure mean; nothing executes. Coordinator.Exec or Coordinator.Run then drives the whole sequence and returns one joined error. Registration is legal only before that call, and the call is legal exactly once. Each violation panics, as does New on a nil System, a ShutdownTimeout that is not positive, or a Readiness already bound to another Coordinator: they are wiring mistakes, not runtime conditions.

Participation

A graph.Dependency takes part only through its Value's methods, in each phase the Value implements: a Starter starts, a Stopper shuts down, and a Subsystem, which is both, does both. A Value that implements one alone is a start-only or stop-only participant, and a Value that implements neither takes no part. A node changes its part only by changing its value, such as a test's substitute through graph's Replace.

Context ownership

The caller owns the signal context: the application's entrypoint builds one — the process package's SignalContext — and passes it through the composition root's run call to Exec or Run. The Coordinator derives the run context from it; the run context is cancelled by the signal, by a startup or monitored failure, or when shutdown begins. Every Start, and Exec's function, receives the run context; work that outlives its call keeps watching that context. The coordinator installs no signal handlers of its own.

Startup and readiness

The layers start lowest first, as graph.System.Layers orders them; a layer's participants start concurrently, and the next layer starts only once the whole layer has. The first start failure cancels the run context, so the rest of its layer can stop early, and no higher layer starts. The errors that wrap context.Canceled and arrive once the run context is cancelled and a failure is on record are dropped: they are consequences of the first failure. The startup failures are joined, each labelled with its dependency's name, and wrapped "startup:".

A context that ends before or during startup stops it: no further layer starts. Run treats that as the signal-driven exit, not a failure, when every startup error wraps the context's error, and returns only the shutdown's errors. Exec returns the context's error wrapped "startup:", since its function never ran.

Readiness never flips during a failed startup, so a probe backed by Coordinator.Ready cannot report a partially started process. On success the coordinator is ready, and Exec and Run alike invoke the OnReady hooks synchronously, in registration order, before Exec's function runs or Run serves. Coordinator satisfies ReadinessChecker, the contract a /readyz endpoint consumes. Readiness is non-monotonic: it is false again the moment shutdown begins.

Coordinator.Checks lists the System's own readiness checks: a Check for each ReadyStarter, a value that implements both Starter and ReadinessChecker, named by its node, in layer order and, within a layer, definition order. Only a Starter's readiness is inferred, since a value reports on what it started: a value with Ready and no Start yields no Check, and a Starter with Ready yields one whether or not it also implements Stopper.

A node that serves readiness, such as a health handler, reaches the Coordinator through a Readiness: the application defines a node whose value is new(lifecycle.Readiness), the zero value, and the handler's node uses it. There is no constructor, since the zero value is complete, and the node's value is the pointer, since binding writes to it. New binds every *Readiness in its System to the Coordinator it returns; a bound Readiness reports that Coordinator's Ready and Checks, and is not among them. An unbound Readiness is not ready and has no checks. A Readiness binds once, so a node whose constructor returns one shared Readiness across two Builds, each given to a New, panics in the second.

Running and monitors

Exec runs its function under the run context once every layer has started, and its error joins Exec's return. Run blocks until the signal context is cancelled (the clean path) or a monitored channel fails.

The monitored channels are those registered with Coordinator.Monitor and the Err channel of every value in the System that implements Monitored, found by New in layer order and, within a layer, definition order. Exec and Run alike watch them once startup has completed: an Err channel is read then, so a value may create it in Start. The first non-nil error any of them yields cancels the run context, ending Exec's function or Run's serving, and joins Exec's or Run's return wrapped "run:". A nil received error is ignored, and a channel closing retires its watcher quietly: that is the expected end of a source that stopped cleanly. A nil channel never yields, so it is not watched.

Shutdown

Shutdown runs on every path: after Exec's function returns, whatever its error, after Run's context ends or a monitor fails, and after a startup failure or cancellation. It cancels the run context, then shuts down every participant of every layer that began to start, the last layer first, each layer concurrently. That includes a participant whose Start failed, so a dependency constructed but not started leaks nothing; the start error stays the error reported.

Shutdown runs under one context derived from context.Background and bounded by Config.ShutdownTimeout, so cleanup has its whole budget regardless of the cancelled run context. Errors are labelled with the dependency's name, joined, wrapped "shutdown:", and joined with Exec's or Run's result, so a failed shutdown fails an otherwise clean run.

The first layer that outlives the deadline adds one error wrapping context.DeadlineExceeded. Its unfinished shutdowns continue on the expired context and their late errors are dropped, because the coordinator cannot stop a goroutine. Each remaining layer still starts its shutdowns on the expired context, so participants that honor their context stop promptly, and shutdown does not wait for them. Run returns nil exactly when a signal-driven exit shut down cleanly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Check added in v0.2.0

type Check struct {
	Name    string
	Checker ReadinessChecker
}

Check pairs a readiness check with the name a probe reports for it; a probe treats a nil Checker as not ready.

type Config added in v0.6.0

type Config struct {
	// ShutdownTimeout bounds the whole shutdown, every layer together.
	ShutdownTimeout config.Duration `json:"shutdown_timeout"`
	Env             Env             `json:"-"`
}

Config is a Coordinator's configuration. Env records the environment-variable names Finalize composed and read; it is excluded from JSON.

func (*Config) Finalize added in v0.6.0

func (c *Config) Finalize(envPrefix string) error

Finalize applies the default ShutdownTimeout of 10s when it is unset, reads the envPrefix-named override, then validates. An unparsable override is an error naming the variable, and a ShutdownTimeout that is not positive is an error.

func (*Config) Merge added in v0.6.0

func (c *Config) Merge(src *Config)

Merge overlays src's set fields onto the receiver.

type Coordinator

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

Coordinator runs one built graph.System: it starts the System's layers, runs a function or serves, and shuts the layers down in reverse. Declare hooks and monitors, then call Exec or Run once.

func New

func New(sys *graph.System, cfg Config) *Coordinator

New returns a Coordinator for sys, shutting down within cfg.ShutdownTimeout, binds every Readiness in sys to it, infers its Coordinator.Checks from the values that implement ReadyStarter, and watches every Monitored value in sys as Coordinator.Monitor would. It panics on a nil sys, a ShutdownTimeout that is not positive, as an unfinalized Config's is, or a Readiness already bound to another Coordinator: each is a wiring mistake.

func (*Coordinator) Checks added in v0.2.0

func (c *Coordinator) Checks() []Check

Checks returns a Check for each value in the System that implements ReadyStarter, that is, both Starter and ReadinessChecker, named by its node's name, in layer order and, within a layer, definition order. A value whose Ready reports on something it does not start, such as a node that only reads another's state, is not among them, and neither is a Readiness: it reports the Coordinator, not a dependency. Whether the value also implements Stopper does not matter. Each call returns a fresh copy, nil when there are none.

func (*Coordinator) Exec added in v0.6.0

func (c *Coordinator) Exec(ctx context.Context, fn func(context.Context) error) error

Exec starts the System, runs fn under the run context, and shuts the System down. Exec keeps Run's runtime contract: the Coordinator is ready and its OnReady hooks have run when fn starts, it is not ready once shutdown begins, and a monitored failure ends fn's context. It returns fn's error as fn returned it, joined with any monitored failure wrapped "run:" and with the shutdown's error. When startup fails, or ctx ends before startup completes, fn does not run and Exec returns the startup error, or ctx's, wrapped "startup:" and joined with the shutdown's. A second call to Exec or Coordinator.Run panics.

func (*Coordinator) Monitor

func (c *Coordinator) Monitor(errs <-chan error)

Monitor registers a channel Coordinator.Exec and Coordinator.Run watch once startup has completed, alongside the System's Monitored values: the first non-nil error received ends the run and joins the return wrapped "run:". A nil error is ignored, and a closed channel retires quietly — the expected end of a source that stopped cleanly. Registration after Exec or Run panics.

func (*Coordinator) OnReady

func (c *Coordinator) OnReady(fn func())

OnReady registers a hook Coordinator.Exec or Coordinator.Run invokes synchronously, in registration order, once startup has completed and the Coordinator is ready: before Exec's function runs, and before Run serves. Registration after Exec or Run panics.

func (*Coordinator) Ready

func (c *Coordinator) Ready() bool

Ready reports whether startup has completed and shutdown has not begun.

func (*Coordinator) Run

func (c *Coordinator) Run(ctx context.Context) error

Run starts the System, serves until ctx ends or a monitored channel fails, and shuts the System down. The end of ctx is the clean stop, and so is a ctx that ends before or during startup: whatever began to start is shut down, and Run returns only the shutdown's error, nil when it is clean. A startup failure returns the startup error wrapped "startup:", and a monitored failure returns it wrapped "run:", each joined with the shutdown's. A second call to Run or Coordinator.Exec panics.

type Env added in v0.6.0

type Env struct {
	ShutdownTimeout string
}

Env names the environment variable Config.Finalize reads. An empty name disables the override.

func NewEnv added in v0.6.0

func NewEnv(prefix string) Env

NewEnv composes <PREFIX>_SHUTDOWN_TIMEOUT with config.EnvName; an empty prefix returns the zero Env.

type Monitored added in v0.6.0

type Monitored interface {
	Err() <-chan error
}

Monitored is a value that reports runtime failures on a channel. A graph.Dependency whose Value implements it is watched by the Coordinator that New returns for its System, as a channel registered with Coordinator.Monitor is: the first non-nil error ends the run. Err is called once startup has completed, so a value may create its channel in Start. A nil error is ignored, a closed channel retires quietly, and a nil channel, which never yields, is not watched.

type Readiness added in v0.6.0

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

Readiness reports a Coordinator's readiness to the nodes that depend on it, such as a health handler. Define it as a graph node whose value is a new *Readiness; New binds every *Readiness in its System to the Coordinator it returns. The zero value is unbound, and an unbound Readiness is not ready and has no checks.

func (*Readiness) Checks added in v0.6.0

func (r *Readiness) Checks() []Check

Checks returns the bound Coordinator's Coordinator.Checks, or nil when r is unbound.

func (*Readiness) Ready added in v0.6.0

func (r *Readiness) Ready() bool

Ready reports the bound Coordinator's Coordinator.Ready, or false when r is unbound.

type ReadinessChecker

type ReadinessChecker interface {
	Ready() bool
}

ReadinessChecker reports whether a subsystem is ready to serve. Coordinator satisfies it, as does any subsystem a readiness probe aggregates.

type ReadyStarter added in v0.8.0

type ReadyStarter interface {
	Starter
	ReadinessChecker
}

ReadyStarter is a Starter that reports whether what it started is ready: the value Coordinator.Checks infers a readiness check from. A value with Ready and no Start is not one.

type Starter added in v0.6.0

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

Starter is a value that starts. A graph.Dependency whose Value implements it takes part in a Coordinator's startup.

type Stopper added in v0.6.0

type Stopper interface {
	Shutdown(ctx context.Context) error
}

Stopper is a value that shuts down. A graph.Dependency whose Value implements it takes part in a Coordinator's shutdown.

type Subsystem added in v0.6.0

type Subsystem interface {
	Starter
	Stopper
}

Subsystem is a value with a whole lifecycle, one that starts and shuts down: a graph.Dependency whose Value implements it takes part in both phases.

Jump to

Keyboard shortcuts

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