-
Notifications
You must be signed in to change notification settings - Fork 41
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Problem Statement
Currently, the application doesn't provide a built-in mechanism for graceful shutdown, which can lead to:
- Abrupt termination of in-flight requests/operations
- Potential data loss or corruption during writes
- Ungraceful disconnect from databases and external services
- Poor experience during deployments or scaling events
When the application receives termination signals (SIGTERM, SIGINT), it should complete ongoing work before exiting rather than terminating immediately.
Proposed Solution
Implement a graceful shutdown mechanism that:
- Listens for OS signals (SIGTERM, SIGINT, SIGQUIT)
- Triggers shutdown sequence with configurable timeout
- Stops accepting new requests while completing existing ones
- Cleanly closes resources (DB connections, message queues, file handles)
- Returns appropriate exit codes based on shutdown success
Proposed Implementation
High-Level Approach
// pkg/shutdown/graceful.go
type GracefulShutdown struct {
timeout time.Duration
signals []os.Signal
cleanup []func(context.Context) error
}
func New(timeout time.Duration) *GracefulShutdown
func (gs *GracefulShutdown) Register(cleanup func(context.Context) error)
func (gs *GracefulShutdown) Wait(ctx context.Context) errorIntegration Example
func main() {
shutdown := graceful.New(30 * time.Second)
// Register cleanup functions
shutdown.Register(server.Shutdown)
shutdown.Register(db.Close)
shutdown.Register(cache.Close)
// Wait for signal
if err := shutdown.Wait(context.Background()); err != nil {
log.Fatal(err)
}
}Benefits
- ✅ Zero-downtime deployments - complete in-flight requests before shutdown
- ✅ Data integrity - ensure transactions complete
- ✅ Better observability - log shutdown progress and issues
- ✅ Configurable timeouts - balance graceful vs. forced termination
- ✅ Composable - works with various server types (HTTP, gRPC, workers)
Implementation Details
Key Components
- Signal Handler: Goroutine listening for OS signals
- Cleanup Registry: Ordered list of cleanup functions
- Context Management: Propagate cancellation with timeout
- Error Aggregation: Collect and report all cleanup errors
- Shutdown Coordinator: Orchestrate the shutdown sequence
Considerations
- Should cleanup functions run in parallel or sequential?
- Proposal: Sequential with order control (dependencies)
- What happens if cleanup exceeds timeout?
- Proposal: Force exit after timeout, log which cleanups didn't complete
- Should we support shutdown hooks at different phases?
- Proposal: Start with simple ordered list, extend if needed
Alternatives Considered
-
Use existing library (e.g.,
github.com/oklog/run)- Pro: Battle-tested
- Con: Additional dependency, may be overkill
-
Minimal signal handling only
- Pro: Simple
- Con: Doesn't coordinate multiple resources
-
Built-in package (proposed)
- Pro: Tailored to project needs, no external deps
- Con: Need to maintain ourselves
Questions for Maintainers
- Does this align with the project's architecture and goals?
- Are there existing patterns in the codebase I should follow?
- Should this be a separate package or integrated into existing code?
- What's the preferred approach for testing shutdown behavior?
- Any specific requirements around signal handling on different platforms?
Next Steps
If this proposal is accepted, I'm happy to:
- Create a detailed design doc
- Submit a PR with implementation
- Add comprehensive tests and documentation
- Provide examples for common use cases
Looking forward to your feedback!
Environment:
- Go version: 1.25+
- Target platforms: Linux, macOS, Windows
References:
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request