controls¶
A small service-lifecycle supervisor for long-running Go processes —
startup ordering, health probes, graceful shutdown, and self-healing restarts,
behind one Controller, as a light, framework-free library.
controls is the same supervisor behind go-tool-base's
service commands, extracted so any project can register its services (HTTP/gRPC
servers, background workers, schedulers) and let the Controller orchestrate
their startup, monitor their health, forward OS signals, and drive a bounded
graceful shutdown — without pulling in the framework.
The light-footprint promise¶
The module graph is deliberately tiny. go.mod declares exactly one external
dependency, cockroachdb/errors — no
config framework, no TUI, no OpenTelemetry, no go-tool-base. A
depfootprint_test.go guard fails the build if any forbidden dependency ever
enters the graph.
The only logging seam is the standard library's *slog.Logger, injected via
WithLogger. Supply one, or supply none — it defaults to a discard handler, so
logging is never mandatory and never a dependency you must adopt.
Design¶
- Framework-free. One external dependency (
cockroachdb/errors); one logging seam (*slog.Logger). Everything else is functional options. - Correct concurrency. Idempotent
Start/Stop; goroutines that terminate on shutdown rather than leak or busy-spin; a restart supervisor that distinguishes clean start / cancellation / failure and never floods the error channel; force-stop at the shutdown deadline; readiness that fails closed until the first async health check has actually run. Race-clean under-race. - Transport-agnostic health.
Status()/Liveness()/Readiness()return plainHealthReportvalues. The module does not open ports or register HTTP/gRPC handlers — you wire the reports into whatever transport your process already speaks (see Add health checks).
Quick start¶
package main
import (
"context"
"gitlab.com/phpboyscout/go/controls"
)
func main() {
c := controls.NewController(context.Background())
c.Register("api",
controls.WithStart(func(ctx context.Context) error {
// start serving; return when ctx is cancelled
<-ctx.Done()
return ctx.Err()
}),
controls.WithStop(func(ctx context.Context) {
// graceful shutdown, bounded by ctx
}),
)
c.Start() // registers SIGINT/SIGTERM handlers by default
c.Wait() // blocks until a signal (or Stop) drains the shutdown sequence
}
Key concepts¶
Controller— the supervisor.Registerservices beforeStart;Waitblocks until the full shutdown sequence has completed.- Health probes — attach
WithStatus/WithLiveness/WithReadinessto a service, or register standaloneHealthChecks (sync, or async with anInterval).Status()/Liveness()/Readiness()aggregate them into aHealthReport. - Restart policy —
WithRestartPolicyenables self-healing with exponential backoff, aMaxRestartscap, a health-failure threshold, and a consecutive-failure counter that resets after a healthy window. - Options —
WithLogger,WithShutdownTimeout,WithoutSignals(tests), andWithValidError(exempt expected terminal errors likehttp.ErrServerClosedfrom the restart count).
Install¶
Where to go next¶
The documentation follows the Diátaxis framework:
- Getting started — a learning-oriented walkthrough: register one service, start it, and shut down cleanly on Ctrl-C.
- How-to guides — task-oriented recipes:
- Explanation — understanding-oriented background:
- Reference — the full API, with runnable
Exampletests, lives on pkg.go.dev.
Using go-tool-base? The framework wires this supervisor into its service commands and exposes the
HealthReports through its ownpkg/httpandpkg/grpchandlers. That glue lives in go-tool-base, not here; this module is transport- and framework-agnostic.