package shutdown import ( "context" "fmt" "sync" "time" "go.uber.org/zap" ) // ShutdownManager gère l'arrêt gracieux de tous les services (BE-SVC-017) type ShutdownManager struct { logger *zap.Logger shutdowners []Shutdowner mu sync.Mutex shuttingDown bool } // Shutdowner est une interface pour les services qui peuvent être arrêtés proprement type Shutdowner interface { Shutdown(ctx context.Context) error Name() string } // NewShutdownManager crée un nouveau gestionnaire de shutdown func NewShutdownManager(logger *zap.Logger) *ShutdownManager { return &ShutdownManager{ logger: logger, shutdowners: make([]Shutdowner, 0), shuttingDown: false, } } // Register enregistre un service pour l'arrêt gracieux func (sm *ShutdownManager) Register(shutdowner Shutdowner) { sm.mu.Lock() defer sm.mu.Unlock() if sm.shuttingDown { sm.logger.Warn("Attempted to register shutdowner during shutdown", zap.String("name", shutdowner.Name())) return } sm.shutdowners = append(sm.shutdowners, shutdowner) sm.logger.Debug("Registered shutdowner", zap.String("name", shutdowner.Name()), zap.Int("total", len(sm.shutdowners))) } // Shutdown arrête tous les services enregistrés de manière gracieuse func (sm *ShutdownManager) Shutdown(ctx context.Context) error { sm.mu.Lock() if sm.shuttingDown { sm.mu.Unlock() return fmt.Errorf("shutdown already in progress") } sm.shuttingDown = true shutdowners := make([]Shutdowner, len(sm.shutdowners)) copy(shutdowners, sm.shutdowners) sm.mu.Unlock() sm.logger.Info("Starting graceful shutdown", zap.Int("services", len(shutdowners))) // Créer un contexte avec timeout global shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() // Arrêter tous les services en parallèle avec timeout individuel var wg sync.WaitGroup errors := make(chan error, len(shutdowners)) for _, shutdowner := range shutdowners { wg.Add(1) go func(s Shutdowner) { defer wg.Done() // Créer un contexte avec timeout pour chaque service serviceCtx, serviceCancel := context.WithTimeout(shutdownCtx, 10*time.Second) defer serviceCancel() sm.logger.Info("Shutting down service", zap.String("service", s.Name())) start := time.Now() if err := s.Shutdown(serviceCtx); err != nil { sm.logger.Error("Error shutting down service", zap.String("service", s.Name()), zap.Error(err)) errors <- fmt.Errorf("%s: %w", s.Name(), err) } else { duration := time.Since(start) sm.logger.Info("Service shut down successfully", zap.String("service", s.Name()), zap.Duration("duration", duration)) } }(shutdowner) } // Attendre que tous les services soient arrêtés ou timeout done := make(chan struct{}) go func() { wg.Wait() close(done) }() timeoutReached := false select { case <-done: sm.logger.Info("All services shut down") case <-shutdownCtx.Done(): sm.logger.Warn("Shutdown timeout reached, some services may not have shut down cleanly") timeoutReached = true } // Collecter les erreurs close(errors) var shutdownErrors []error for err := range errors { shutdownErrors = append(shutdownErrors, err) } // Ajouter une erreur si le timeout est atteint if timeoutReached && len(shutdownErrors) == 0 { shutdownErrors = append(shutdownErrors, fmt.Errorf("shutdown timeout reached")) } if len(shutdownErrors) > 0 { return fmt.Errorf("shutdown completed with %d errors: %v", len(shutdownErrors), shutdownErrors) } return nil } // ShutdownFunc est une fonction helper pour créer un Shutdowner depuis une fonction type ShutdownFunc struct { name string fn func(ctx context.Context) error } // NewShutdownFunc crée un Shutdowner depuis une fonction func NewShutdownFunc(name string, fn func(ctx context.Context) error) Shutdowner { return &ShutdownFunc{ name: name, fn: fn, } } // Shutdown exécute la fonction de shutdown func (sf *ShutdownFunc) Shutdown(ctx context.Context) error { return sf.fn(ctx) } // Name retourne le nom du service func (sf *ShutdownFunc) Name() string { return sf.name }