diff --git a/internal/app/app.go b/internal/app/app.go index d5a1459..bc59aca 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,9 +9,11 @@ import ( "codeword/internal/services" "codeword/internal/worker/purge_worker" "codeword/internal/worker/recovery_worker" + "codeword/pkg/closer" "context" - "go.mongodb.org/mongo-driver/mongo" + "errors" "go.uber.org/zap" + "time" ) func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { @@ -26,6 +28,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { ctx, cancel := context.WithCancel(ctx) defer cancel() + shutdownGroup := closer.NewCloserGroup() + mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) @@ -70,7 +74,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { Mongo: mdb.Collection("codeword"), }) - purgeWC := purge_worker.NewRecoveryWC(purge_worker.Deps{ + purgeWC := purge_worker.NewPurgeWC(purge_worker.Deps{ Logger: logger, Mongo: mdb.Collection("codeword"), }) @@ -87,44 +91,28 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { go func() { if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil { logger.Error("Server startup error", zap.Error(err)) + cancel() } }() + shutdownGroup.Add(closer.CloserFunc(server.Shutdown)) + shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect)) + shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop)) + shutdownGroup.Add(closer.CloserFunc(purgeWC.Stop)) + <-ctx.Done() - if err := shutdownApp(ctx, server, mdb, logger); err != nil { - return err - } - logger.Info("The application has stopped") - return nil -} - -// TODO возможно стоит вынести в отдельные файлы или отказаться от разделения на отдельные методы - -func shutdownApp(ctx context.Context, server *httpserver.Server, mdb *mongo.Database, logger *zap.Logger) error { - if err := shutdownHTTPServer(ctx, server, logger); err != nil { - return err - } - - if err := shutdownMongoDB(ctx, mdb, logger); err != nil { - return err - } - - return nil -} - -func shutdownHTTPServer(ctx context.Context, server *httpserver.Server, logger *zap.Logger) error { - if err := server.Shutdown(ctx); err != nil { - logger.Error("Error stopping HTTP server", zap.Error(err)) - return err - } - return nil -} - -func shutdownMongoDB(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error { - if err := mdb.Client().Disconnect(ctx); err != nil { - logger.Error("Error when closing MongoDB connection", zap.Error(err)) + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer timeoutCancel() + if err := shutdownGroup.Call(timeoutCtx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + logger.Error("Shutdown timed out", zap.Error(err)) + } else { + logger.Error("Failed to shutdown services gracefully", zap.Error(err)) + } return err } + + logger.Info("Application has stopped") return nil } diff --git a/internal/worker/purge_worker/purge_worker.go b/internal/worker/purge_worker/purge_worker.go index e621641..017c7f5 100644 --- a/internal/worker/purge_worker/purge_worker.go +++ b/internal/worker/purge_worker/purge_worker.go @@ -18,7 +18,7 @@ type PurgeWorker struct { mongo *mongo.Collection } -func NewRecoveryWC(deps Deps) *PurgeWorker { +func NewPurgeWC(deps Deps) *PurgeWorker { return &PurgeWorker{ logger: deps.Logger, mongo: deps.Mongo, @@ -54,3 +54,7 @@ func (wc *PurgeWorker) processTasks(ctx context.Context) { wc.logger.Info("Deleted documents", zap.Int64("count", result.DeletedCount)) } } + +func (wc *PurgeWorker) Stop(ctx context.Context) error { + return nil +} diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 7237278..72e1933 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -114,3 +114,7 @@ func (wc *RecoveryWorker) sendRecoveryTask(ctx context.Context, task models.Reco //wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email)) return nil } + +func (wc *RecoveryWorker) Stop(ctx context.Context) error { + return nil +} diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -0,0 +1,37 @@ +package closer + +import ( + "context" +) + +type Closer interface { + Close(ctx context.Context) error +} + +type CloserFunc func(ctx context.Context) error + +func (cf CloserFunc) Close(ctx context.Context) error { + return cf(ctx) +} + +type CloserGroup struct { + closers []Closer +} + +func NewCloserGroup() *CloserGroup { + return &CloserGroup{} +} + +func (cg *CloserGroup) Add(c Closer) { + cg.closers = append(cg.closers, c) +} + +func (cg *CloserGroup) Call(ctx context.Context) error { + var closeErr error + for i := len(cg.closers) - 1; i >= 0; i-- { + if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil { + closeErr = err + } + } + return closeErr +}