fix(iot): не рвать автореконнект при сбое подписки (🔴6)

OnConnectHandler вызывал c.Disconnect(100) при таймауте/ошибке подписки. В paho
это ЯВНОЕ отключение — автореконнект после него не срабатывает, и bridge
«глохнет» (соединения нет, подписки нет, /ready=true).

Теперь: подписка ретраится 3 раза с паузой 2с; при сбое — ждём следующего
реконнекта (OnConnectHandler снова вызовется). Disconnect не вызывается.
This commit is contained in:
Repinoid
2026-10-10 18:55:34 +03:00
parent 2cca7cb856
commit 6048777a9e
+21 -11
View File
@@ -19,6 +19,10 @@ import (
// telemetryTopicFilter — все namespace, все устройства. // telemetryTopicFilter — все namespace, все устройства.
const telemetryTopicFilter = "+/telemetry/+" const telemetryTopicFilter = "+/telemetry/+"
// subscribeRetryInterval — пауза между попытками подписки при сбое.
// Подписка обязательна (иначе bridge «глохнет»: соединение есть, а топик не слушается).
const subscribeRetryInterval = 2 * time.Second
// Run — бесконечный цикл bridge: подключение → подписка → ожидание сигнала завершения. // Run — бесконечный цикл bridge: подключение → подписка → ожидание сигнала завершения.
func Run(ctx context.Context, cfg *config.Config, sqsClient *sqs.Client, log *slog.Logger) error { func Run(ctx context.Context, cfg *config.Config, sqsClient *sqs.Client, log *slog.Logger) error {
log.Info("bridge: starting", "broker", cfg.MQTTBrokerURL, "client_id", cfg.MQTTClientID) log.Info("bridge: starting", "broker", cfg.MQTTBrokerURL, "client_id", cfg.MQTTClientID)
@@ -86,18 +90,24 @@ func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, hand
}) })
opts.SetOnConnectHandler(func(c mqtt.Client) { opts.SetOnConnectHandler(func(c mqtt.Client) {
log.Info("bridge: MQTT connected") log.Info("bridge: MQTT connected")
token := c.Subscribe(telemetryTopicFilter, 1, handler) // Подписка обязательна. Ретраим ограниченно. НЕ вызываем c.Disconnect():
if !token.WaitTimeout(10 * time.Second) { // в paho это ЯВНОЕ отключение, после которого автореконнект НЕ срабатывает
log.Error("bridge: resubscribe timeout", "filter", telemetryTopicFilter) // — bridge оставался бы отключённым навсегда (ревью 2026-10-10, 🔴6).
c.Disconnect(100) // принудительный реконнект (подписка обязательна) // При сбое ждём следующего реконнекта (OnConnectHandler вызовется заново).
return for attempt := 1; attempt <= 3; attempt++ {
token := c.Subscribe(telemetryTopicFilter, 1, handler)
if token.WaitTimeout(10*time.Second) && token.Error() == nil {
log.Info("bridge: subscribed", "filter", telemetryTopicFilter, "attempt", attempt)
return
}
log.Error("bridge: subscribe failed", "filter", telemetryTopicFilter, "attempt", attempt, "err", token.Error())
select {
case <-ctx.Done():
return
case <-time.After(subscribeRetryInterval):
}
} }
if token.Error() != nil { log.Error("bridge: subscribe failed after retries, will retry on next reconnect", "filter", telemetryTopicFilter)
log.Error("bridge: resubscribe failed", "filter", telemetryTopicFilter, "err", token.Error())
c.Disconnect(100) // принудительный реконнект (подписка обязательна)
return
}
log.Info("bridge: subscribed", "filter", telemetryTopicFilter)
}) })
client := mqtt.NewClient(opts) client := mqtt.NewClient(opts)