fix(iot): готовность consumer по SQS + ограничение wg.Wait + идемпотентный Close (Этап 3)
- consumer.Run получил onReady: /ready теперь false при ошибке ReceiveMessage (раньше флаг consumer'а выставлялся до старта и не отражал SQS). - main.go: wg.Wait заменён на waitWithTimeout (shutdownGrace=15с) — зависшую подсистему не ждём бесконечно. - iotpg.Close идемпотентен (sync.Once) — повторный вызов не паникует.
This commit is contained in:
+27
-5
@@ -30,6 +30,22 @@ import (
|
||||
"gitea.services.ngcloud.ru/Nail/IoT/internal/storage/iotpg"
|
||||
)
|
||||
|
||||
// shutdownGrace — максимум ожидания остановки подсистем при завершении. Если
|
||||
// что-то зависло — не блокируем выход (ревью 2026-10-10, «ограничить wg.Wait»).
|
||||
const shutdownGrace = 15 * time.Second
|
||||
|
||||
// waitWithTimeout ждёт wg не дольше d; false = не дождались.
|
||||
func waitWithTimeout(wg *sync.WaitGroup, d time.Duration) bool {
|
||||
done := make(chan struct{})
|
||||
go func() { wg.Wait(); close(done) }()
|
||||
select {
|
||||
case <-done:
|
||||
return true
|
||||
case <-time.After(d):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// version — подставляется через ldflags при сборке.
|
||||
var version = "dev"
|
||||
|
||||
@@ -136,9 +152,10 @@ func run() int {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ready.SetConsumer(true)
|
||||
// ready.SetConsumer управляется consumer'ом: true после успешного
|
||||
// ReceiveMessage, false при ошибке SQS (находка «readiness»).
|
||||
defer ready.SetConsumer(false)
|
||||
if err := consumer.Run(ctx, cfg, sqsClient, iotStore, log); err != nil && !errors.Is(err, context.Canceled) {
|
||||
if err := consumer.Run(ctx, cfg, sqsClient, iotStore, log, ready.SetConsumer); err != nil && !errors.Is(err, context.Canceled) {
|
||||
log.Error("consumer stopped", "err", err)
|
||||
fatal.Store(true)
|
||||
cancel()
|
||||
@@ -151,7 +168,10 @@ func run() int {
|
||||
// (фикс MEDIUM из ревью 2026-08-16).
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
wg.Wait()
|
||||
// Ждём остановки подсистем, но не бесконечно (зависшую не ждём).
|
||||
if !waitWithTimeout(&wg, shutdownGrace) {
|
||||
log.Warn("iot-service: подсистемы не завершились за отведённое время", "grace", shutdownGrace)
|
||||
}
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownCancel()
|
||||
_ = server.Shutdown(shutdownCtx)
|
||||
@@ -161,11 +181,13 @@ func run() int {
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error("http server", "err", err)
|
||||
cancel() // останавливаем bridge/consumer
|
||||
wg.Wait()
|
||||
waitWithTimeout(&wg, shutdownGrace)
|
||||
return 1
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if !waitWithTimeout(&wg, shutdownGrace) {
|
||||
log.Warn("iot-service: подсистемы не завершились за отведённое время", "grace", shutdownGrace)
|
||||
}
|
||||
if fatal.Load() {
|
||||
// Не os.Exit: возвращаем код, чтобы отработали defer-ы (iotStore.Close →
|
||||
// финальный flush последнего батча). os.Exit их бы обошёл.
|
||||
|
||||
Reference in New Issue
Block a user