app
Package app runs an application's long-running work and shuts it down in a defined order.
Constructors do blocking setup (open a pool, load keys) and register what they open on a Cleanup stack. Long-running work, such as an HTTP server or a job worker, implements Runner. Run starts the runners and, when a signal arrives, the context ends or a runner fails, performs the shutdown sequence documented on Run.
A typical composition root:
cleanup := &app.Cleanup{}
db, err := postgres.Open(ctx, dsn)
if err != nil {
return errors.Join(err, cleanup.Close(ctx))
}
cleanup.AddCloser("postgres", db)
return app.Run(ctx, []app.Runner{server, workers}, app.WithCleanup(cleanup))
Stability: pre-1.0; the API may change in minor releases (ADR-0015). Design: ADR-0017.
Constants#
const DefaultDrainDelay, …#
const (
DefaultDrainDelay = 5 * time.Second
DefaultShutdownTimeout = 25 * time.Second
)Defaults used by Run.
Variables#
var ErrRunnerExited, …#
var (
// ErrRunnerExited reports that a runner returned before shutdown started.
ErrRunnerExited = errors.New("app: runner exited before shutdown")
// ErrShutdownTimeout reports that runners did not stop within the
// shutdown timeout.
ErrShutdownTimeout = errors.New("app: shutdown timed out")
// ErrForcedShutdown reports that a second signal interrupted graceful
// shutdown.
ErrForcedShutdown = errors.New("app: shutdown forced by signal")
)Errors reported by Run. Check them with errors.Is.
Functions#
func Run#
func Run(ctx context.Context, runners []Runner, opts ...Option) errorRun starts every runner and blocks until ctx is done, a configured signal arrives, or a runner stops. It then shuts down in this order:
- Shutdown hooks run (for example, marking the service not ready).
- The drain delay elapses, giving load balancers time to stop routing. It is skipped when shutdown started because a runner stopped.
- The runners' context is cancelled; Run waits up to the shutdown timeout.
- The cleanup stack closes resources in reverse order of registration.
A signal received during shutdown skips the remaining waits and adds ErrForcedShutdown. Runners receive a context that keeps ctx's values but is cancelled only in step 3.
Run returns nil after a clean shutdown. Otherwise it returns every failure joined: runner errors, ErrRunnerExited, ErrShutdownTimeout, ErrForcedShutdown and cleanup errors. Runner errors that wrap context.Canceled after step 3 are not failures.
Types#
type Cleanup#
type Cleanup struct {
// contains filtered or unexported fields
}Cleanup is a stack of resources to release, closed in reverse order of registration. The zero value is ready to use. It is safe for concurrent use.
func (*Cleanup) Add#
func (c *Cleanup) Add(name string, fn func(ctx context.Context) error)Add registers fn to run on Cleanup.Close. The name appears in errors.
func (*Cleanup) AddCloser#
func (c *Cleanup) AddCloser(name string, closer io.Closer)AddCloser registers an io.Closer.
func (*Cleanup) Close#
func (c *Cleanup) Close(ctx context.Context) errorClose runs the registered functions in reverse order and removes them. Every function runs even if an earlier one fails; the errors are joined. Calling Close again runs only functions added since the previous call.
type Option#
type Option interface {
// contains filtered or unexported methods
}An Option configures Run.
func OnShutdown#
func OnShutdown(hook func()) OptionOnShutdown adds a hook that runs when shutdown starts, before the drain delay. Use it to fail readiness checks. Hooks run in the order added and must return quickly.
func WithCleanup#
func WithCleanup(c *Cleanup) OptionWithCleanup sets the stack Run closes as the last shutdown step.
func WithDrainDelay#
func WithDrainDelay(d time.Duration) OptionWithDrainDelay sets how long Run waits after shutdown hooks before cancelling runners. Zero disables the delay. Default: DefaultDrainDelay.
func WithLogger#
func WithLogger(logger *slog.Logger) OptionWithLogger sets the logger for lifecycle events. Default: discard.
func WithShutdownTimeout#
func WithShutdownTimeout(d time.Duration) OptionWithShutdownTimeout sets how long Run waits for runners to stop, and separately how long the cleanup stack may take. Default: DefaultShutdownTimeout.
func WithSignals#
func WithSignals(sigs ...os.Signal) OptionWithSignals sets the signals that start shutdown. With no arguments, Run ignores signals. Default: os.Interrupt and SIGTERM.
type Runner#
type Runner interface {
Run(ctx context.Context) error
}A Runner is long-running work such as an HTTP server or a job worker.
Run blocks until ctx is done, then stops gracefully and returns nil. Any other return, including returning before ctx is done, is a failure.
type RunnerFunc#
type RunnerFunc func(ctx context.Context) errorRunnerFunc adapts a function to the Runner interface.
func (RunnerFunc) Run#
func (f RunnerFunc) Run(ctx context.Context) errorRun calls f(ctx).