Grace is a comprehensive Go library for graceful startup and shutdown of services. It provides both low-level utilities for building services and high-level orchestration for managing multiple services.
-
Shepherd - High-level service orchestrator
- Manages multiple services with unified lifecycle
- Signal handling (SIGINT, SIGTERM, etc.)
- Configurable shutdown timeout
- Custom logger support
-
Utility Functions - Low-level building blocks
GraceWaitGroup- Enhanced WaitGroup withGo()methodWaitWithTimeout- Wait for WaitGroup with timeoutRunPeriodicTask- Execute tasks on a scheduleRunService- Manage multiple concurrent tasks
go get github.com/jimmicro/gracePerfect for managing multiple independent services:
package main
import (
"context"
"time"
"github.com/jimmicro/grace"
)
// Implement the Grace interface for your service
type MyService struct{}
func (s *MyService) Run(ctx context.Context) error {
// Your service logic here
<-ctx.Done()
return nil
}
func (s *MyService) Shutdown(ctx context.Context) error {
// Cleanup logic here
return nil
}
func (s *MyService) Name() string {
return "MyService"
}
func main() {
services := []grace.Grace{
&MyService{},
}
shepherd := grace.NewShepherd(services,
grace.WithTimeout(30*time.Second),
grace.WithLogger(&customLogger{}),
)
shepherd.Start(context.Background())
}Perfect for building custom services with fine-grained control:
package main
import (
"context"
"log"
"time"
"github.com/jimmicro/grace"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg grace.GraceWaitGroup
// Start a periodic task
wg.Go(func() {
grace.RunPeriodicTask(
ctx,
"MyTask",
5*time.Second,
func(ctx context.Context, t time.Time) error {
log.Println("Task executed at", t)
return nil
},
grace.WithRunOnStart(true),
grace.WithStopOnTaskError(false),
)
})
// Wait for completion
wg.Wait()
}shepherd := grace.NewShepherd(services, options...)Options:
WithTimeout(duration)- Set shutdown timeout (default: 30s)WithSignals(signals...)- Set signals to listen forWithLogger(logger)- Set custom logger
Services managed by Shepherd must implement:
type Grace interface {
Run(ctx context.Context) error // Start the service
Shutdown(ctx context.Context) error // Graceful shutdown
Name() string // Service name for logging
}Enhanced sync.WaitGroup with automatic management:
var wg grace.GraceWaitGroup
// Automatically calls Add(1) and Done()
wg.Go(func() {
// Your goroutine code
})
wg.Wait()Wait for WaitGroup with context timeout:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := grace.WaitWithTimeout(&wg.WaitGroup, ctx)
if err != nil {
// Timeout occurred
}Execute a task periodically:
err := grace.RunPeriodicTask(
ctx,
"TaskName",
interval,
taskFunc,
grace.WithRunOnStart(true), // Run immediately on start
grace.WithStopOnTaskError(false), // Continue on error
grace.WithTaskLogger(logger), // Custom logger
)Task Function Signature:
func(ctx context.Context, t time.Time) errorManage multiple concurrent tasks:
tasks := []func(context.Context) error{
task1,
task2,
task3,
}
err := grace.RunService(
ctx,
"ServiceName",
&graceWg,
tasks,
grace.WithStopOnFirstError(true), // Stop all on first error
grace.WithServiceLogger(logger), // Custom logger
)type HTTPServer struct {
server *http.Server
}
func (h *HTTPServer) Run(ctx context.Context) error {
errCh := make(chan error, 1)
go func() {
if err := h.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
errCh <- err
}
}()
select {
case <-ctx.Done():
return nil
case err := <-errCh:
return err
}
}
func (h *HTTPServer) Shutdown(ctx context.Context) error {
return h.server.Shutdown(ctx)
}
func (h *HTTPServer) Name() string {
return "HTTPServer"
}See examples/http-mux/main.go for complete example.
type PeriodicService struct {
wg grace.GraceWaitGroup
}
func (s *PeriodicService) Run(ctx context.Context) error {
return grace.RunPeriodicTask(
ctx,
"PeriodicTask",
5*time.Second,
func(ctx context.Context, t time.Time) error {
// Your task logic
return nil
},
)
}See examples/utils-demo/main.go for complete example.
type MultiTaskService struct {
wg grace.GraceWaitGroup
}
func (s *MultiTaskService) Run(ctx context.Context) error {
tasks := []func(context.Context) error{
s.task1,
s.task2,
s.task3,
}
return grace.RunService(ctx, "MultiTaskService", &s.wg, tasks)
}
func (s *MultiTaskService) task1(ctx context.Context) error {
return grace.RunPeriodicTask(ctx, "Task1", 3*time.Second, s.doTask1)
}See examples/single-service/demo_service.go for complete example.
grace/
├── Shepherd (High-level orchestration)
│ ├── Service lifecycle management
│ ├── Signal handling
│ └── Timeout control
│
└── Utilities (Low-level building blocks)
├── GraceWaitGroup - Enhanced WaitGroup
├── WaitWithTimeout - Timeout waiting
├── RunPeriodicTask - Periodic execution
└── RunService - Task management
- Composability - Utilities can be used independently or together
- Flexibility - Both high-level and low-level APIs available
- Safety - Proper context handling and timeout control
- Observability - Configurable logging throughout
- Simplicity - Clean, idiomatic Go code
-
Always use context for cancellation
// Good func task(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() case <-time.After(time.Second): // Do work } }
-
Set appropriate timeouts
shepherd := grace.NewShepherd(services, grace.WithTimeout(30*time.Second), // Allow enough time for cleanup )
-
Handle errors properly
err := grace.RunPeriodicTask(ctx, "Task", interval, taskFunc, grace.WithStopOnTaskError(false), // Continue on non-critical errors )
-
Use GraceWaitGroup for automatic management
var wg grace.GraceWaitGroup wg.Go(func() { /* work */ }) // Cleaner than wg.Add(1) + defer wg.Done()
Contributions are welcome! Please ensure:
- All code has English comments
- Tests are included for new features
- Examples demonstrate new functionality
- Code follows Go best practices
MIT License - see LICENSE file for details