graceful is a Go package for handling application lifecycle, signal-driven shutdown, and structured exit events.
It provides a single Run entrypoint that runs your application, listens for exit signals, and coordinates graceful → cancel → forced shutdown sequences with configurable timeouts.
- Run your application with clean lifecycle management.
- Capture termination signals (
SIGTERM,SIGINT). - Execute graceful, cancel, and forced shutdown logic in order.
- Configurable timeouts for each phase.
- Structured events (
ExitType,Action) for observability. - Optional hooks (
EventsFunc,ExitFunc) for telemetry and cleanup.
When receiving an exit signal or programmatic exit, graceful coordinates shutdown in phases:
If GracefulFunc is configured and GracefulTimeout > 0, it is called.
Typical use: stop accepting new work, but let ongoing operations finish.
If your application does not exit before the timeout, the cancel phase begins.
If CancelFunc is configured and CancelTimeout > 0, it is called.
Typical use: cancel contexts, return errors to clients, clean up resources.
If your application does not exit before the timeout, the forced phase begins.
If ForcedFunc is configured and ForcedTimeout > 0, it is called.
Typical use: log an error about stuck operations, release critical resources.
If your application still does not exit, the program is aborted.
Immediately exits the application.
Note, a second signal received at any time also immediately aborts the application.
go get github.com/rgzr/gracefulpackage main
import (
"fmt"
"time"
"github.com/rgzr/graceful"
)
func main() {
graceful.Run(&graceful.Config{
RunFunc: func() error {
fmt.Println("application running")
// Simulate workload until killed
select {}
},
GracefulFunc: func() {
fmt.Println("stopping new work, letting ongoing operations finish")
// maybe call *http.Server.Shutdown() here
},
CancelFunc: func() {
fmt.Println("cancelling ongoing operations")
// maybe pass the app context here and cancel it
},
ForcedFunc: func() {
fmt.Println("forcing shutdown, some operations may be lost")
// maybe signal parents to stop waiting for children here and return errors instead
},
GracefulTimeout: 5 * time.Second,
CancelTimeout: 3 * time.Second,
ForcedTimeout: 1 * time.Second,
EventsFunc: func(e *graceful.Event) {
fmt.Printf("event: exitType=%s action=%s\n", e.ExitType, e.Action)
},
ExitFunc: func(err error) {
fmt.Printf("exiting with error: %v\n", err)
},
})
}