Documentation
¶
Overview ¶
Package shutdown is a phased, parallel-within-phase, observable graceful shutdown manager for long-running Go services.
The package has zero third-party dependencies. Logger, OTEL, Prometheus, health, and HTTP framework integrations live in adapter modules under contrib/.
Typical use:
mgr := shutdown.New(
shutdown.WithBudget(30 * time.Second),
)
mgr.Register("http", srv.Shutdown, shutdown.WithPhase(shutdown.PhaseStopAccepting))
mgr.Register("nats", natsConn.Drain, shutdown.WithPhase(shutdown.PhaseDrainTraffic))
mgr.Register("db", db.Close, shutdown.WithPhase(shutdown.PhaseCloseClients))
mgr.Register("otel", otelP.Shutdown, shutdown.WithPhase(shutdown.PhaseFlushLogs))
if err := mgr.Listen(ctx); err != nil {
log.Fatal(err)
}
See the README and the companion examples repo at github.com/ubgo/shutdown-examples for k8s preStop, drain, OTEL tracing, and worker-actor patterns.
Index ¶
- Variables
- type ActorHandle
- type ActorOption
- type ErrorPolicy
- type HandlerFunc
- type InterruptFunc
- type Logger
- type Manager
- func (m *Manager) Listen(ctx context.Context) error
- func (m *Manager) OnSignal(sig os.Signal, fn func(ctx context.Context, sig os.Signal))
- func (m *Manager) Register(name string, fn HandlerFunc, opts ...RegisterOption) error
- func (m *Manager) RegisterActor(name string, interrupt InterruptFunc, opts ...ActorOption) (*ActorHandle, error)
- func (m *Manager) Shutdown(ctx context.Context) error
- func (m *Manager) Subscribe(o Observer)
- type Observer
- type Option
- func WithBudget(d time.Duration) Option
- func WithErrorPolicy(p ErrorPolicy) Option
- func WithExitOnComplete(successCode, failureCode int) Option
- func WithForceOnSecondSignal(enabled bool, forceCode int) Option
- func WithHandlerDefaultTimeout(d time.Duration) Option
- func WithLogger(l Logger) Option
- func WithSerial(phase Phase) Option
- func WithSignals(sigs ...os.Signal) Option
- func WithWatchdogGrace(d time.Duration) Option
- type PanicError
- type Phase
- type RegisterOption
- type RunFunc
Constants ¶
This section is empty.
Variables ¶
var ErrAlreadyRegistered = errors.New("shutdown: handler already registered with that name")
ErrAlreadyRegistered is returned by Register when a handler with the given name is already in the Manager.
var ErrClosed = errors.New("shutdown: manager closed (shutdown in progress or completed)")
ErrClosed is returned by Register when the Manager has already started running its phases.
var ErrEmptyName = errors.New("shutdown: handler name must be non-empty")
ErrEmptyName is returned by Register when name is the empty string.
Functions ¶
This section is empty.
Types ¶
type ActorHandle ¶
type ActorHandle struct {
// contains filtered or unexported fields
}
ActorHandle is returned from RegisterActor. Call Done(err) when the actor's run loop has exited so the manager can proceed to the next phase.
func (*ActorHandle) Done ¶
func (h *ActorHandle) Done(err error)
Done signals that the actor's run loop has returned. err is the run loop's exit error (nil if it returned cleanly). Idempotent — only the first call has effect.
type ActorOption ¶
type ActorOption func(*actorRegistration)
ActorOption configures a RegisterActor call.
func WithActorPhase ¶
func WithActorPhase(p Phase) ActorOption
WithActorPhase places the actor's interrupt step in a specific phase. Default: PhaseDrainTraffic.
func WithActorTimeout ¶
func WithActorTimeout(d time.Duration) ActorOption
WithActorTimeout caps how long the manager waits for the actor's run loop to confirm completion via Done after interrupt is called. Default: 30s.
type ErrorPolicy ¶
type ErrorPolicy int
ErrorPolicy decides what happens when a handler returns an error.
const ( // ContinueOnError keeps running remaining handlers in the same phase // and proceeds to subsequent phases. All errors are aggregated via // errors.Join and returned at the end. Default. ContinueOnError ErrorPolicy = iota // StopOnError aborts the phase on the first failure and returns // immediately, skipping subsequent phases. StopOnError )
type HandlerFunc ¶
HandlerFunc is the unit of work registered with a Manager. It receives a context bounded by the per-handler timeout (or the remaining global budget, whichever is shorter) and should return promptly when ctx is done.
type InterruptFunc ¶
type InterruptFunc func(err error)
InterruptFunc is the cancellation half of an actor registration. It is called when the manager wants the actor to stop. Implementations should be quick and idempotent.
type Logger ¶
type Logger interface {
Info(msg string, fields ...any)
Warn(msg string, fields ...any)
Error(msg string, fields ...any)
}
Logger is the minimal logging contract. Adapters ship as separate modules: shutdown-zap, shutdown-slog. The default Manager uses log/slog.
func NoopLogger ¶
func NoopLogger() Logger
NoopLogger returns a Logger that discards all messages. Useful in tests or when the application logs shutdown events through its observer hooks instead of the Logger interface.
func SlogLogger ¶
SlogLogger wraps a *slog.Logger as a shutdown.Logger. Useful when the caller wants to pass a specific *slog.Logger rather than relying on slog.Default().
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the central shutdown coordinator.
Construct with New, register handlers (and optionally actors), then call Listen (blocking on signals) or Shutdown (programmatic). A Manager is safe for concurrent Register and Subscribe calls before Listen/Shutdown has been entered; once a shutdown is in progress further Register calls return ErrClosed.
func (*Manager) Listen ¶
Listen blocks until one of the configured shutdown signals arrives or ctx is cancelled, then runs all phases in order. Returns the aggregated error (errors.Join of every handler error) or context.Canceled if the caller cancelled before completion.
Listen does NOT call os.Exit by default; opt in via WithExitOnComplete.
While shutdown is running a second shutdown signal triggers an immediate os.Exit(forceCode) when WithForceOnSecondSignal is enabled (default).
Listen is safe to call once per Manager; subsequent calls return ErrClosed.
Signals registered via OnSignal are automatically added to the listened set, so callers do not need to also include them in WithSignals — a hook for SIGHUP without WithSignals(syscall.SIGHUP, ...) still fires.
func (*Manager) OnSignal ¶
OnSignal registers a hook for a non-shutdown signal (e.g. SIGHUP for config reload, SIGUSR1 for log rotation). When the signal arrives the hook is invoked with the Listen ctx; the shutdown sequence is NOT triggered, and Listen continues waiting for further signals.
The signal is automatically added to the listened set, so callers do not need to also include it in WithSignals.
Note: signals registered here are no longer treated as shutdown triggers by the manager. If a user adds SIGTERM via OnSignal it will not start a shutdown — it will only call the user's hook. To restore shutdown behaviour for that signal, simply do not register a hook for it.
func (*Manager) Register ¶
func (m *Manager) Register(name string, fn HandlerFunc, opts ...RegisterOption) error
Register adds a shutdown handler. Returns an error if name is empty, already registered, or the Manager has already started shutting down.
func (*Manager) RegisterActor ¶
func (m *Manager) RegisterActor(name string, interrupt InterruptFunc, opts ...ActorOption) (*ActorHandle, error)
RegisterActor registers a long-running actor (goroutine-style service) with the Manager. The actor is signalled to stop via interrupt during its phase, and the manager waits up to the per-actor timeout (or the remaining global budget, whichever is shorter) for the actor to confirm completion via the returned ActorHandle.
Typical use:
handle, err := mgr.RegisterActor("worker", workerStop,
shutdown.WithActorPhase(shutdown.PhaseDrainTraffic))
go func() {
err := workerLoop() // blocks until workerStop is called
handle.Done(err) // signals actor exited
}()
Note: the run loop itself is not held by the manager. The caller is responsible for spawning the goroutine that runs the work; the manager only owns the interrupt + completion handshake.
type Observer ¶
type Observer struct {
OnSignal func(sig os.Signal)
OnPhaseStart func(phase Phase, handlerCount int)
OnPhaseEnd func(phase Phase, dur time.Duration, errs []error)
OnHandlerStart func(name string, phase Phase)
OnHandlerEnd func(name string, phase Phase, dur time.Duration, err error)
OnComplete func(totalDur time.Duration, err error)
}
Observer fan-out for adapters. All callbacks are optional and may be nil. Observers fire synchronously; long-running observers should fan out to a goroutine themselves.
type Option ¶
type Option func(*config)
Option configures a Manager.
func WithBudget ¶
WithBudget sets the total wall-clock budget across all phases. After the budget expires, in-flight handler contexts are cancelled and the watchdog hard-exits the process after a 1-second grace period (configurable via WithWatchdogGrace). Default: 30s.
func WithErrorPolicy ¶
func WithErrorPolicy(p ErrorPolicy) Option
WithErrorPolicy overrides the ContinueOnError default.
func WithExitOnComplete ¶
WithExitOnComplete makes Listen call os.Exit at the end of shutdown. successCode is used when the aggregated error is nil; failureCode otherwise. Default: never exit (just return).
func WithForceOnSecondSignal ¶
WithForceOnSecondSignal makes a second signal during shutdown trigger an immediate os.Exit(forceCode). Default: true with forceCode=130.
Set enabled=false to ignore second signals (the orchestrator's SIGKILL is then the only escape hatch — useful only when you trust the watchdog budget completely).
func WithHandlerDefaultTimeout ¶ added in v0.2.0
WithHandlerDefaultTimeout sets the default per-handler timeout used when a Register call does not pass WithTimeout. Default: 5s.
func WithLogger ¶
WithLogger overrides the default slog-backed logger.
func WithSerial ¶
WithSerial opts a specific phase out of parallel handler execution. By default all handlers in a phase run in parallel.
func WithSignals ¶
WithSignals overrides the listened signal set. Default: SIGINT, SIGTERM.
If you want to add a non-shutdown signal hook (e.g. SIGHUP for reload), use Manager.OnSignal instead — that registers a hook without making the signal trigger a shutdown.
func WithWatchdogGrace ¶
WithWatchdogGrace sets the grace period after the budget expires before the watchdog calls os.Exit. Default: 1s.
type PanicError ¶ added in v0.2.0
PanicError wraps a recovered panic from inside a shutdown handler. The runner returns this in place of the handler's intended error so the panic surfaces in the aggregated error and observers' OnHandlerEnd hook.
func (*PanicError) Error ¶ added in v0.2.0
func (e *PanicError) Error() string
type Phase ¶
type Phase int
Phase is the ordering key for handler execution. Lower phases run first. Predefined constants cover the common k8s preStop drain pattern; a raw int is also valid for power users who want finer-grained sequencing.
const ( PhasePreShutdown Phase = -100 PhaseStopAccepting Phase = 0 PhaseDrainTraffic Phase = 100 PhaseFlushQueues Phase = 200 PhaseCloseClients Phase = 300 PhaseFlushLogs Phase = 400 PhasePostShutdown Phase = 500 )
Predefined phases — match the typical k8s graceful-shutdown flow:
- PhasePreShutdown — flip drain flag (load balancer stops sending).
- PhaseStopAccepting — close listeners (no new requests accepted).
- PhaseDrainTraffic — wait for in-flight work to finish.
- PhaseFlushQueues — flush async producers and worker queues.
- PhaseCloseClients — close DB, cache, messaging clients.
- PhaseFlushLogs — flush logs and traces last so prior phase errors reach the collector.
- PhasePostShutdown — final cleanup, exit-code reporting.
type RegisterOption ¶
type RegisterOption func(*registration)
RegisterOption configures a Register call.
func WithPhase ¶
func WithPhase(p Phase) RegisterOption
WithPhase places the handler in a specific phase. Default: PhaseCloseClients.
func WithTimeout ¶
func WithTimeout(d time.Duration) RegisterOption
WithTimeout caps how long this handler may run. Default is set by WithHandlerDefaultTimeout on the Manager (5s out of the box). The actual deadline is min(WithTimeout, remaining global budget).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
contrib
|
|
|
shutdown-gin
module
|
|
|
shutdown-nethttp
module
|
|
|
shutdown-otel
module
|
|
|
shutdown-prom
module
|
|
|
shutdown-zap
module
|