https://github.com/struct0x/exitplan
A Go library for managing the lifecycle of an application with graceful shutdown capabilities.
https://github.com/struct0x/exitplan
context go golang graceful-shutdown
Last synced: 5 months ago
JSON representation
A Go library for managing the lifecycle of an application with graceful shutdown capabilities.
- Host: GitHub
- URL: https://github.com/struct0x/exitplan
- Owner: struct0x
- License: mit
- Created: 2025-09-13T12:23:54.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-10-24T18:25:32.000Z (8 months ago)
- Last Synced: 2025-10-24T20:22:34.694Z (8 months ago)
- Topics: context, go, golang, graceful-shutdown
- Language: Go
- Homepage:
- Size: 9.77 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Exitplan
[](https://pkg.go.dev/github.com/struct0x/exitplan)

A Go library for managing the lifecycle of an application with graceful shutdown capabilities.
## Overview
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
## Installation
```bash
go get github.com/struct0x/exitplan
```
## Lifecycle Phases
Exitplan splits application lifetime into three phases, each with its own context:
- **Starting**: before `Run()` begins. Use `StartingContext()` for initialization.
It is canceled immediately when `Run()` starts.
- **Running**: active between `Run()` and `Exit()`. Use `Context()` for workers and other long-running tasks.
It is canceled as soon as shutdown begins.
- **Teardown**: after `Exit()` is called. Use `TeardownContext()` in shutdown callbacks.
It is canceled when the global teardown timeout elapses.
### Callback ordering
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.
## Usage
### Basic Example
```go
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)
}
```
### Advanced Example with Context
```go
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)
}
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.