A Go library for managing the lifecycle of an application with graceful shutdown capabilities.
The Exitplan library provides a simple mechanism for managing the lifetime of an application. It helps you handle application startup, running, and shutdown phases with proper resource cleanup.
Key features include:
- Distinct application lifecycle phases (starting, running, teardown)
- Context-based lifecycle management
- Graceful shutdown with customizable timeout
- Flexible callback registration for cleanup operations
- Signal handling for clean application termination
- Synchronous and asynchronous shutdown callbacks
- Error handling during shutdown
go get github.com/struct0x/exitplanExitplan splits application lifetime into three phases, each with its own context:
-
Starting: before
Run()begins. UseStartingContext()for initialization.
It is canceled immediately whenRun()starts. -
Running: active between
Run()andExit(). UseContext()for workers and other long-running tasks.
It is canceled as soon as shutdown begins. -
Teardown: after
Exit()is called. UseTeardownContext()in shutdown callbacks.
It is canceled when the global teardown timeout elapses.
Shutdown callbacks registered with OnExit* are executed in LIFO order (last registered, first executed).
This mirrors resource lifecycles: if you start DB then HTTP, shutdown runs HTTP then DB.
Callbacks marked with Async are awaited up to the teardown timeout.
package main
import (
"fmt"
"syscall"
"time"
"github.com/struct0x/exitplan"
)
func main() {
// Create a new Exitplan instance with signal handling for graceful shutdown
ex := exitplan.New(
exitplan.WithSignal(syscall.SIGINT, syscall.SIGTERM),
exitplan.WithTeardownTimeout(5*time.Second),
)
// Register cleanup functions
ex.OnExit(func() {
fmt.Println("Cleaning up resources...")
time.Sleep(1 * time.Second)
fmt.Println("Cleanup complete")
})
// Start your application
fmt.Println("Application starting...")
// Run the application (blocks until Exit() is called)
exitCause := ex.Run()
fmt.Printf("Application exited: %v\n", exitCause)
}package main
import (
"context"
"fmt"
"syscall"
"time"
"github.com/struct0x/exitplan"
)
func main() {
// Create a new Exitplan instance with options
ex := exitplan.New(
exitplan.WithSignal(syscall.SIGINT, syscall.SIGTERM),
exitplan.WithTeardownTimeout(10*time.Second),
exitplan.WithExitError(func(err error) {
fmt.Printf("Error during shutdown: %v\n", err)
}),
)
// Use the starting context for initialization
startingCtx := ex.StartingContext()
_ = startingCtx
// Initialize resources with the starting context
// For example, pinging a database connection to ensure it is ready, yet it should not freeze the application
// err := db.Ping(startingCtx)
// Register cleanup with context awareness
ex.OnExitWithContext(func(ctx context.Context) {
fmt.Println("Starting cleanup...")
select {
case <-time.After(2 * time.Second):
fmt.Println("Cleanup completed successfully")
case <-ctx.Done():
fmt.Println("Cleanup was interrupted by timeout")
}
})
// Register cleanup that might return an error
ex.OnExitWithContextError(func(ctx context.Context) error {
fmt.Println("Closing database connection...")
time.Sleep(1 * time.Second)
return nil
})
// Register an async cleanup task
ex.OnExit(func() {
fmt.Println("Performing async cleanup...")
time.Sleep(3 * time.Second)
fmt.Println("Async cleanup complete")
}, exitplan.Async)
// Start your application
fmt.Println("Application starting...")
// Get the running context to use in your application
runningCtx := ex.Context()
// Start a worker that respects the application lifecycle
workerDone := make(chan struct{})
go func() {
for {
select {
case <-runningCtx.Done():
fmt.Println("Worker shutting down...")
time.Sleep(100 * time.Millisecond) // Simulate some work
close(workerDone)
return
case <-time.After(1 * time.Second):
fmt.Println("Worker doing work...")
}
}
}()
ex.OnExitWithContext(func(ctx context.Context) {
select {
case <-workerDone:
fmt.Println("Worker shutdown complete")
case <-ctx.Done():
fmt.Println("Worker shutdown interrupted")
}
})
// Run the application (blocks until Exit() is called)
exitCause := ex.Run()
fmt.Printf("Application exited: %v\n", exitCause)
}This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.