Documentation
¶
Overview ¶
Package graterm provides a structured API for managing graceful application shutdown in response to specific os.Signal events. It ensures a controlled and predictable shutdown process by allowing the registration of ordered termination hooks.
Purpose ¶
The graterm package simplifies application shutdown handling by introducing a centralized shutdown manager, the Terminator. It listens for specified os.Signal events and orchestrates the orderly execution of termination hooks. Hooks allow resources to be properly cleaned up before the application exits.
Key Concepts ¶
- Terminator: A singleton shutdown manager responsible for capturing OS termination signals and executing registered hooks.
- Hook: A termination callback function that performs cleanup tasks during shutdown.
- Order: A numeric priority assigned to each hook, dictating execution order. Hooks with the same Order execute concurrently, while hooks with a lower Order value complete before those with a higher Order value.
Features ¶
- Enforced Hook Registration: Hooks must only be registered via Terminator methods, ensuring proper ordering and execution.
- Ordered Execution: Hooks execute sequentially based on their assigned Order; those with the same priority run concurrently.
- Configurable Timeouts: Each Hook may have an individual timeout, and a global shutdown timeout can be set to enforce an upper limit on the termination process.
- Optional Logging: the library does not depend on a specific logging framework.
- Traceability: Hooks can be assigned a name for logging purposes when a Logger is attached to the Terminator.
- Panic Safety: Panics inside hooks are caught, logged, and do not disrupt the overall shutdown sequence.
Additional Considerations ¶
- Concurrent execution of hooks with the same Order requires careful management of shared resources to avoid race conditions.
- Incorrect timeout configurations may cause delays in shutdown; ensure timeouts are set to appropriate values.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Hook ¶ added in v0.4.0
type Hook struct {
// contains filtered or unexported fields
}
Hook is a registered ordered termination unit of work. I.e., the code that needs to be executed to perform resource cleanup or any other maintenance while shutting down the application.
Do NOT create a Hook instance manually, use Terminator.WithOrder() method instead to get a Hook instance.
func (*Hook) Register ¶ added in v0.4.0
Register registers termination Hook that should finish execution in less than given timeout.
Timeout duration must be greater than zero; if not, timeout of 1 min will be used.
The context value passed into hookFunc will be used only for cancellation signaling. I.e. to signal that Terminator will no longer wait on Hook to finish termination.
Example ¶
package main import ( "context" "log" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // Define Order: const ( HTTPServerTerminationOrder graterm.Order = 1 ) // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) // Register some hooks: terminator.WithOrder(HTTPServerTerminationOrder). Register(1*time.Second, func(ctx context.Context) { log.Println("terminating HTTP Server...") defer log.Println("...HTTP Server terminated") }) // Wait for os.Signal to occur, then terminate application with maximum timeout of 20 seconds: if err := terminator.Wait(appCtx, 20*time.Second); err != nil { log.Printf("graceful termination period was timed out: %+v", err) } }
func (*Hook) WithName ¶ added in v0.4.0
WithName sets (optional) human-readable name of the registered termination Hook.
The Hook name will be useful only if Logger instance has been injected (using Terminator.SetLogger method) into Terminator to log internal termination lifecycle events.
Example ¶
package main import ( "context" "log" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // Define Order: const ( HTTPServerTerminationOrder graterm.Order = 1 ) // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) terminator.SetLogger(log.Default()) // Optional step // Register some hooks: terminator.WithOrder(HTTPServerTerminationOrder). WithName("HTTP Server"). // Define (optional) Hook name Register(1*time.Second, func(ctx context.Context) { log.Println("terminating HTTP Server...") defer log.Println("...HTTP Server terminated") }) // Wait for os.Signal to occur, then terminate application with maximum timeout of 20 seconds: if err := terminator.Wait(appCtx, 20*time.Second); err != nil { log.Printf("graceful termination period was timed out: %+v", err) } }
type Logger ¶
Logger specifies the interface for internal Terminator log operations.
By default, library will not log anything. To set the logger, use Terminator.SetLogger() method.
type Order ¶ added in v0.4.0
type Order int
Order is an application components termination order. Termination Hooks registered with the same order will be executed concurrently.
Lower order - higher priority.
type Terminator ¶ added in v0.3.0
type Terminator struct {
// contains filtered or unexported fields
}
Terminator is a component terminator that executes registered termination Hooks in a specified order.
func NewWithSignals ¶
NewWithSignals creates a new instance of component Terminator.
If the given appCtx parameter is canceled, the termination process will start for already registered Hook instances after calling Terminator.Wait method.
Example of useful signals might be: syscall.SIGINT, syscall.SIGTERM.
Note: this method will start internal monitoring goroutine.
Example ¶
package main import ( "context" "log" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) // register hooks... // Wire other components ... // Wait for os.Signal to occur, then terminate application with maximum timeout of 40 seconds: if err := terminator.Wait(appCtx, 40*time.Second); err != nil { log.Printf("graceful termination period was timed out: %+v", err) } }
func (*Terminator) SetLogger ¶ added in v0.3.0
func (t *Terminator) SetLogger(log Logger)
SetLogger sets the Logger implementation.
If log is nil, then NOOP logger implementation will be used.
Example ¶
package main import ( "context" "log" "syscall" "github.com/skovtunenko/graterm" ) func main() { // create new Terminator instance: terminator, _ := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) // Set custom logger implementation instead of default NOOP one: terminator.SetLogger(log.Default()) }
func (*Terminator) Wait ¶ added in v0.3.0
Wait blocks execution until the provided appCtx is canceled and then executes all registered termination hooks.
This function initiates the shutdown sequence when appCtx is done, typically due to receiving an os.Signal events. After appCtx is canceled, it waits for all registered hooks to complete execution within the specified shutdownTimeout.
Hooks are executed in order of priority (lower order values execute first). Hooks with the same order run concurrently. If the shutdownTimeout expires before all hooks complete, the function returns an error.
This is a blocking call that should be placed at the end of the application's lifecycle to ensure a proper shutdown.
Parameters:
- appCtx: The application context that, when canceled, triggers the termination process.
- shutdownTimeout: The maximum time allowed for all hooks to complete execution.
Returns:
- error: If termination exceeds the shutdownTimeout, an error is returned indicating a timeout.
Example ¶
package main import ( "context" "log" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) // register hooks... // Wire other components ... // Wait for os.Signal to occur, then terminate application with maximum timeout of 40 seconds: if err := terminator.Wait(appCtx, 40*time.Second); err != nil { log.Printf("graceful termination period was timed out: %+v", err) } }
func (*Terminator) WithOrder ¶ added in v0.3.0
func (t *Terminator) WithOrder(order Order) *Hook
WithOrder sets the Order for the termination hook. It starts registration chain to register termination hook with priority.
The lower the Order the higher the execution priority, the earlier it will be executed. If there are multiple hooks with the same Order they will be executed in parallel.
Example (GenericApplicationComponents) ¶
package main import ( "context" "log" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // Define Orders: const ( HTTPServerTerminationOrder graterm.Order = 1 MessagingTerminationOrder graterm.Order = 1 DBTerminationOrder graterm.Order = 2 ) // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) terminator.SetLogger(log.Default()) // Optional step // Register HTTP Server termination hook: terminator.WithOrder(HTTPServerTerminationOrder). WithName("HTTP Server"). // setting a Name is optional and will be useful only if logger instance provided Register(1*time.Second, func(ctx context.Context) { log.Println("terminating HTTP Server...") defer log.Println("...HTTP Server terminated") }) // Register nameless Messaging termination hook: terminator.WithOrder(MessagingTerminationOrder). Register(1*time.Second, func(ctx context.Context) { log.Println("terminating Messaging...") defer log.Println("...Messaging terminated") }) // Register Database termination hook: terminator.WithOrder(DBTerminationOrder). WithName("DB"). // setting a Name is optional and will be useful only if logger instance provided Register(1*time.Second, func(ctx context.Context) { log.Println("terminating DB...") defer log.Println("...DB terminated") const sleepTime = 3 * time.Second select { case <-time.After(sleepTime): log.Printf("DB termination sleep time %v is over\n", sleepTime) case <-ctx.Done(): log.Printf("DB termination Context is Done because of: %+v\n", ctx.Err()) } }) // Wait for os.Signal to occur, then terminate application with maximum timeout of 20 seconds: if err := terminator.Wait(appCtx, 20*time.Second); err != nil { log.Printf("graceful termination period was timed out: %+v", err) } }
Example (HttpServer) ¶
package main import ( "context" "errors" "fmt" "log" "net/http" "syscall" "time" "github.com/skovtunenko/graterm" ) func main() { // Define Order for HTTP Server termination: const HTTPServerTerminationOrder graterm.Order = 1 // create new Terminator instance: terminator, appCtx := graterm.NewWithSignals(context.Background(), syscall.SIGINT, syscall.SIGTERM) terminator.SetLogger(log.Default()) // Optional step // Create an HTTP Server and add one simple handler into it: httpServer := &http.Server{ Addr: ":8080", Handler: http.DefaultServeMux, } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello, world!") }) // Start HTTP server in a separate goroutine: go func() { if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("terminated HTTP Server: %+v\n", err) } }() // Register HTTP Server termination hook: terminator.WithOrder(HTTPServerTerminationOrder). WithName("HTTPServer"). // setting a Name is optional and will be useful only if logger instance provided Register(10*time.Second, func(ctx context.Context) { if err := httpServer.Shutdown(ctx); err != nil { log.Printf("shutdown HTTP Server: %+v\n", err) } }) // Wait for os.Signal to occur, then terminate application with maximum timeout of 30 seconds: if err := terminator.Wait(appCtx, 30*time.Second); err != nil { log.Printf("graceful termination period is timed out: %+v\n", err) } }