2025-06-05 21:21:28 +07:00

143 lines
3.2 KiB
Go

// Package lifecycle provides application lifecycle management using tomb
package lifecycle
import (
"context"
"os"
"os/signal"
"sync"
"syscall"
"time"
"go.uber.org/multierr"
"gopkg.in/tomb.v2"
"starter-kit/internal/helper/logger"
)
// Lifecycle manages the application lifecycle
type Lifecycle struct {
tomb tomb.Tomb
shutdownTimeout time.Duration
mu sync.Mutex
services []Service
}
// Service represents a service that can be started and stopped
type Service interface {
Name() string
Start() error
Shutdown(ctx context.Context) error
}
// New creates a new Lifecycle with the given shutdown timeout
func New(shutdownTimeout time.Duration) *Lifecycle {
return &Lifecycle{
shutdownTimeout: shutdownTimeout,
services: make([]Service, 0),
}
}
// Register adds a service to the lifecycle manager
func (l *Lifecycle) Register(service Service) {
l.mu.Lock()
defer l.mu.Unlock()
l.services = append(l.services, service)
}
// Start starts all registered services
func (l *Lifecycle) Start() error {
l.mu.Lock()
defer l.mu.Unlock()
// Start all services
for _, svc := range l.services {
service := svc // Create a new variable for the closure
l.tomb.Go(func() error {
logger.Info("Starting service: ", service.Name())
if err := service.Start(); err != nil {
logger.Errorf("Service %s failed to start: %v", service.Name(), err)
return err
}
logger.Info("Service started: ", service.Name())
return nil
})
}
return nil
}
// Wait blocks until all services have stopped
func (l *Lifecycle) Wait() error {
return l.tomb.Wait()
}
// Shutdown gracefully shuts down all services
func (l *Lifecycle) Shutdown() error {
l.mu.Lock()
defer l.mu.Unlock()
logger.Info("Initiating graceful shutdown...")
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), l.shutdownTimeout*time.Second)
defer cancel()
// Notify all services to shut down
l.tomb.Kill(nil)
// Shutdown all services in reverse order
var errs error
for i := len(l.services) - 1; i >= 0; i-- {
svc := l.services[i]
logger.Infof("Shutting down service: %s", svc.Name())
if err := svc.Shutdown(ctx); err != nil {
logger.Errorf("Error shutting down %s: %v", svc.Name(), err)
errs = multierr.Append(errs, err)
}
}
// Wait for all services to stop or timeout
errChan := make(chan error, 1)
go func() {
errChan <- l.tomb.Wait()
}()
select {
case err := <-errChan:
if err != nil {
errs = multierr.Append(errs, err)
}
case <-ctx.Done():
errs = multierr.Append(errs, ctx.Err())
}
if errs != nil {
logger.Errorf("Shutdown completed with errors: %v", errs)
} else {
logger.Info("Shutdown completed successfully")
}
return errs
}
// ShutdownOnSignal shuts down the lifecycle when a signal is received
func (l *Lifecycle) ShutdownOnSignal(signals ...os.Signal) {
if len(signals) == 0 {
signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, signals...)
l.tomb.Go(func() error {
select {
case sig := <-sigChan:
logger.Infof("Received signal: %v. Initiating shutdown...", sig)
return l.Shutdown()
case <-l.tomb.Dying():
return nil
}
})
}