Documentation
¶
Overview ¶
Package ctrl provides a set of control functions for assertions, error handling, HTTP server management, and graceful shutdown handling in Go applications. Built for Go 1.21+, it offers a clean API with flexible configuration options and no external runtime dependencies.
Assertions ¶
The package provides assertion functions that panic when conditions are not met, useful for runtime invariant checking:
ctrl.Assert(user.IsAuthenticated())
ctrl.Assertf(count > 0, "expected positive count, got %d", count)
ctrl.AssertFunc(func() bool { return database.IsConnected() })
ctrl.AssertFuncf(func() bool { return cache.Size() < maxSize }, "cache exceeded: %d", cache.Size())
Error Handling ¶
For scenarios where returning an error is more appropriate than panicking, the package provides ErrorOr variants:
if err := ctrl.ErrorOr(user.IsAuthenticated()); err != nil {
return err
}
if err := ctrl.ErrorOrf(count > 0, "expected positive count, got %d", count); err != nil {
return err
}
if err := ctrl.ErrorOrFunc(func() bool { return database.IsConnected() }); err != nil {
return err
}
if err := ctrl.ErrorOrFuncf(func() bool { return cache.Size() < maxSize },
"cache size exceeded: %d/%d", cache.Size(), maxSize); err != nil {
return err
}
customErr := errors.New("database not connected")
if err := ctrl.ErrorOrWithErr(database.IsConnected(), customErr); err != nil {
return err // Returns customErr if condition fails
}
if err := ctrl.ErrorOrFuncWithErr(func() bool { return cache.Size() < maxSize }, ErrCacheFull); err != nil {
return err // Returns ErrCacheFull if condition fails
}
HTTP Server Management ¶
The package helps manage HTTP server lifecycle, particularly graceful shutdown:
// Shutdown an HTTP server with a timeout
err := ctrl.ShutdownHTTPServer(ctx, server,
ctrl.WithHTTPShutdownTimeout(5*time.Second))
// Run a server with context-aware shutdown
errCh := ctrl.RunHTTPServerWithContext(ctx, server,
func() error { return server.ListenAndServe() },
ctrl.WithHTTPShutdownTimeout(5*time.Second),
ctrl.WithHTTPLogger(logger))
Graceful Shutdown ¶
The package provides robust handling of process termination signals:
// Basic setup
ctx, cancel := ctrl.GracefulShutdown()
defer cancel()
// With custom configuration
ctx, cancel := ctrl.GracefulShutdown(
ctrl.WithTimeout(30*time.Second),
ctrl.WithSignals(syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP),
ctrl.WithExitCode(2),
ctrl.WithOnShutdown(func(sig os.Signal) {
log.Printf("shutting down due to %s signal", sig)
database.Close()
}),
ctrl.WithOnForceExit(func() {
log.Printf("force exiting after timeout")
}),
ctrl.WithLogger(logger))
Best Practices ¶
Use assertions for internal invariants that should never fail in correct code:
ctrl.Assert(len(buffer) >= headerSize) // Internal invariant
Use ErrorOr variants for validating external input or recoverable conditions:
if err := ctrl.ErrorOr(len(userInput) < maxLength); err != nil {
return err // External input validation
}
For HTTP servers, combine graceful shutdown with server lifecycle management:
ctx, cancel := ctrl.GracefulShutdown(ctrl.WithTimeout(10*time.Second))
defer cancel()
errCh := ctrl.RunHTTPServerWithContext(ctx, server, server.ListenAndServe)
if err := <-errCh; err != nil {
log.Fatalf("server error: %v", err)
}
Example (GracefulShutdown) ¶
Example_gracefulShutdown demonstrates basic usage of the GracefulShutdown function.
package main
import (
"fmt"
"log/slog"
"os"
"time"
"github.com/go-pkgz/ctrl"
)
func main() {
// normally you would use slog.Default(), but for the example we'll create a no-op logger
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
// set up graceful shutdown
_, cancel := ctrl.GracefulShutdown(
ctrl.WithLogger(logger),
ctrl.WithTimeout(5*time.Second),
)
defer cancel()
fmt.Println("Application is running")
fmt.Println("When SIGINT or SIGTERM is received, shutdown will be initiated")
fmt.Println("Example complete (no actual signal sent)")
}
Output: Application is running When SIGINT or SIGTERM is received, shutdown will be initiated Example complete (no actual signal sent)
Example (GracefulShutdownCustomConfiguration) ¶
Example_gracefulShutdownCustomConfiguration demonstrates custom shutdown configuration.
package main
import (
"fmt"
"log/slog"
"os"
"time"
"github.com/go-pkgz/ctrl"
)
func main() {
// create a noop logger for the example
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
// set up graceful shutdown with several options
_, cancel := ctrl.GracefulShutdown(
ctrl.WithLogger(logger),
ctrl.WithTimeout(3*time.Second),
ctrl.WithExitCode(2), // Non-zero exit code
ctrl.WithoutForceExit(), // Disable forced exit
ctrl.WithSignals(os.Interrupt), // Only listen for Ctrl+C, not SIGTERM
)
defer cancel()
fmt.Println("Application running with custom shutdown configuration")
fmt.Println("- 3 second timeout")
fmt.Println("- Exit code 2")
fmt.Println("- Forced exit disabled")
fmt.Println("- Only listening for SIGINT")
// for the example to complete
fmt.Println("Example complete (no signal sent)")
}
Output: Application running with custom shutdown configuration - 3 second timeout - Exit code 2 - Forced exit disabled - Only listening for SIGINT Example complete (no signal sent)
Example (GracefulShutdownWithCallbacks) ¶
Example_gracefulShutdownWithCallbacks demonstrates using callbacks during shutdown.
package main
import (
"fmt"
"log/slog"
"os"
"time"
"github.com/go-pkgz/ctrl"
)
func main() {
// for testing examples
exampleDone := make(chan struct{})
_, cancel := ctrl.GracefulShutdown(
ctrl.WithLogger(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))),
ctrl.WithTimeout(3*time.Second),
ctrl.WithOnShutdown(func(sig os.Signal) {
fmt.Printf("Shutdown initiated by signal: %v\n", sig)
fmt.Println("Closing database connections...")
time.Sleep(10 * time.Millisecond) // Simulate work
}),
ctrl.WithOnForceExit(func() {
fmt.Println("Forced shutdown - cleanup incomplete!")
}),
)
defer cancel()
fmt.Println("Application running with shutdown callbacks configured")
// for the example only, we'll manually cancel the context
go func() {
time.Sleep(50 * time.Millisecond)
cancel() // Simulate a shutdown signal
fmt.Println("Simulated shutdown signal")
close(exampleDone)
}()
// for the example to complete
<-exampleDone
fmt.Println("Context canceled, starting graceful shutdown")
}
Output: Application running with shutdown callbacks configured Simulated shutdown signal Context canceled, starting graceful shutdown
Example (HttpServerWithContext) ¶
Example_httpServerWithContext demonstrates how to run an HTTP server that shuts down gracefully when the parent context is canceled.
package main
import (
"bytes"
"context"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/go-pkgz/ctrl"
)
func main() {
// create a context that we can cancel to trigger shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// create a simple HTTP handler
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
})
// create server
server := &http.Server{
Addr: "localhost:0", // random port
Handler: mux,
}
// create a custom logger for the example
var logBuf bytes.Buffer
logger := slog.New(slog.NewTextHandler(&logBuf, &slog.HandlerOptions{Level: slog.LevelInfo}))
// start server with options
errCh := ctrl.RunHTTPServerWithContext(
ctx,
server,
func() error {
return server.ListenAndServe()
},
ctrl.WithHTTPShutdownTimeout(15*time.Second),
ctrl.WithHTTPLogger(logger),
)
// for example only - trigger shutdown after a brief delay
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("Triggering shutdown...")
cancel()
}()
// wait for server to exit and check for errors
err := <-errCh
if err != nil {
fmt.Println("Server error:", err)
} else {
fmt.Println("Server stopped gracefully")
}
}
Output: Triggering shutdown... Server stopped gracefully
Index ¶
- func Assert(condition bool)
- func AssertFunc(f func() bool)
- func AssertFuncf(f func() bool, format string, args ...any)
- func Assertf(condition bool, format string, args ...any)
- func ErrorOr(condition bool) error
- func ErrorOrFunc(f func() bool) error
- func ErrorOrFuncWithErr(f func() bool, err error) error
- func ErrorOrFuncf(f func() bool, format string, args ...any) error
- func ErrorOrWithErr(condition bool, err error) error
- func ErrorOrf(condition bool, format string, args ...any) error
- func GracefulShutdown(opts ...ShutdownOption) (context.Context, context.CancelFunc)
- func RunHTTPServerWithContext(ctx context.Context, server *http.Server, startFn func() error, ...) <-chan error
- func ShutdownHTTPServer(ctx context.Context, server *http.Server, opts ...HTTPOption) error
- type HTTPOption
- type ShutdownOption
- func WithExitCode(code int) ShutdownOption
- func WithLogger(logger *slog.Logger) ShutdownOption
- func WithOnForceExit(fn func()) ShutdownOption
- func WithOnShutdown(fn func(os.Signal)) ShutdownOption
- func WithSignals(signals ...os.Signal) ShutdownOption
- func WithTimeout(timeout time.Duration) ShutdownOption
- func WithoutForceExit() ShutdownOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssertFuncf ¶
AssertFuncf panics if the function returns false, with a formatted message.
func ErrorOrFunc ¶
ErrorOrFunc returns nil if the function returns true, otherwise returns an error.
func ErrorOrFuncWithErr ¶
ErrorOrFuncWithErr returns nil if the function returns true, otherwise returns the given error.
func ErrorOrFuncf ¶
ErrorOrFuncf returns nil if the function returns true, otherwise returns an error with a formatted message.
func ErrorOrWithErr ¶
ErrorOrWithErr returns nil if condition is true, otherwise returns the given error.
func ErrorOrf ¶
ErrorOrf returns nil if condition is true, otherwise returns an error with a formatted message.
func GracefulShutdown ¶
func GracefulShutdown(opts ...ShutdownOption) (context.Context, context.CancelFunc)
GracefulShutdown handles process termination with graceful shutdown. It returns a context that is canceled when a termination signal is received and a cancel function that can be called to trigger shutdown manually.
func RunHTTPServerWithContext ¶
func RunHTTPServerWithContext(ctx context.Context, server *http.Server, startFn func() error, opts ...HTTPOption) <-chan error
RunHTTPServerWithContext runs a server start function and ensures it shuts down gracefully when the provided context is canceled. The startFn is responsible for starting the server (e.g., ListenAndServe). It returns a channel that will receive any error from the server.
func ShutdownHTTPServer ¶
ShutdownHTTPServer gracefully shuts down an HTTP server with a timeout. It returns any error encountered during shutdown.
Types ¶
type HTTPOption ¶
type HTTPOption func(*httpOptions)
HTTPOption represents a functional option for HTTP server operations.
func WithHTTPLogger ¶
func WithHTTPLogger(logger *slog.Logger) HTTPOption
WithHTTPLogger sets a custom logger for HTTP server operations.
func WithHTTPShutdownTimeout ¶
func WithHTTPShutdownTimeout(timeout time.Duration) HTTPOption
WithHTTPShutdownTimeout sets the maximum time to wait for server shutdown.
type ShutdownOption ¶
type ShutdownOption func(*shutdownConfig)
ShutdownOption configures shutdown behavior
func WithExitCode ¶
func WithExitCode(code int) ShutdownOption
WithExitCode sets the exit code used for forced exits
func WithLogger ¶
func WithLogger(logger *slog.Logger) ShutdownOption
WithLogger sets a custom slog.Logger for shutdown messages
func WithOnForceExit ¶
func WithOnForceExit(fn func()) ShutdownOption
WithOnForceExit sets a callback function that is called right before forced exit
func WithOnShutdown ¶
func WithOnShutdown(fn func(os.Signal)) ShutdownOption
WithOnShutdown sets a callback function that is called when shutdown begins
func WithSignals ¶
func WithSignals(signals ...os.Signal) ShutdownOption
WithSignals sets which signals trigger the shutdown
func WithTimeout ¶
func WithTimeout(timeout time.Duration) ShutdownOption
WithTimeout sets the maximum time to wait for graceful shutdown
func WithoutForceExit ¶
func WithoutForceExit() ShutdownOption
WithoutForceExit disables the forced exit after timeout