diff --git a/cmd/iot-service/main.go b/cmd/iot-service/main.go index 5c1febe..f24ac33 100644 --- a/cmd/iot-service/main.go +++ b/cmd/iot-service/main.go @@ -33,11 +33,18 @@ import ( // version — подставляется через ldflags при сборке. var version = "dev" +// main — тонкая обёртка: os.Exit обходит defer-ы, поэтому вся логика в run(), +// которая возвращает код выхода и выполняет свои defer-ы (devices/iotStore +// Close, финальный flush) ДО выхода (ревью 🔴5: «os.Exit обходит defer-ы»). func main() { + os.Exit(run()) +} + +func run() int { cfg, err := config.Load() if err != nil { slog.Error("config", "err", err) - os.Exit(1) + return 1 } log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel})) @@ -54,7 +61,7 @@ func main() { storeCancel() if err != nil { log.Error("devices store", "err", err) - os.Exit(1) + return 1 } defer devices.Close() log.Info("devices store ready") @@ -65,14 +72,14 @@ func main() { ensureCancel() if err != nil { log.Error("ensure bridge device", "err", err) - os.Exit(1) + return 1 } log.Info("bridge device ensured", "username", cfg.MQTTUsername) iotStore, err := iotpg.New(cfg.IOTPGDSN, log) if err != nil { log.Error("iotpg store", "err", err) - os.Exit(1) + return 1 } defer iotStore.Close() log.Info("telemetry store ready") @@ -146,13 +153,18 @@ func main() { log.Info("iot-service listening", "addr", server.Addr) if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Error("http server", "err", err) - os.Exit(1) + cancel() // останавливаем bridge/consumer + wg.Wait() + return 1 } wg.Wait() if fatal.Load() { + // Не os.Exit: возвращаем код, чтобы отработали defer-ы (iotStore.Close → + // финальный flush последнего батча). os.Exit их бы обошёл. log.Error("iot-service: подсистема завершилась с ошибкой — выход ≠ 0") - os.Exit(1) + return 1 } log.Info("iot-service stopped") + return 0 }