fix(iot): /ready отражает факт подписки MQTT (находка #3)
Было: main выставлял ready.SetBridge(true) ПЕРЕД bridge.Run, поэтому «соединён, но подписка не прошла» давал /ready=200 и молча терял телеметрию до реконнекта. Теперь bridge сам управляет флагом (колбэк onSubscribed): true только после успешного SUBSCRIBE, false при потере соединения/сбое подписки.
This commit is contained in:
@@ -118,9 +118,11 @@ func run() int {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ready.SetBridge(true)
|
// ready.SetBridge управляется самим bridge: true только после успешной
|
||||||
|
// подписки (находка #3). Иначе «соединён, но не подписан» давал /ready=200
|
||||||
|
// и молча терял телеметрию.
|
||||||
defer ready.SetBridge(false)
|
defer ready.SetBridge(false)
|
||||||
if err := bridge.Run(ctx, cfg, sqsClient, log); err != nil && !errors.Is(err, context.Canceled) {
|
if err := bridge.Run(ctx, cfg, sqsClient, log, ready.SetBridge); err != nil && !errors.Is(err, context.Canceled) {
|
||||||
log.Error("bridge stopped", "err", err)
|
log.Error("bridge stopped", "err", err)
|
||||||
fatal.Store(true)
|
fatal.Store(true)
|
||||||
cancel() // штатный shutdown, затем выход ≠ 0
|
cancel() // штатный shutdown, затем выход ≠ 0
|
||||||
|
|||||||
@@ -23,8 +23,18 @@ const telemetryTopicFilter = "+/telemetry/+"
|
|||||||
// Подписка обязательна (иначе bridge «глохнет»: соединение есть, а топик не слушается).
|
// Подписка обязательна (иначе bridge «глохнет»: соединение есть, а топик не слушается).
|
||||||
const subscribeRetryInterval = 2 * time.Second
|
const subscribeRetryInterval = 2 * time.Second
|
||||||
|
|
||||||
// Run — бесконечный цикл bridge: подключение → подписка → ожидание сигнала завершения.
|
// setSubscribed — nil-safe вызов колбэка состояния подписки.
|
||||||
func Run(ctx context.Context, cfg *config.Config, sqsClient *sqs.Client, log *slog.Logger) error {
|
func setSubscribed(fn func(bool), v bool) {
|
||||||
|
if fn != nil {
|
||||||
|
fn(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run — бесконечный цикл bridge. onSubscribed — колбэк состояния подписки
|
||||||
|
// (true после успешного SUBSCRIBE, false при потере соединения/сбое подписки):
|
||||||
|
// /ready должен отражать реальную подписку, а не факт запуска горутины
|
||||||
|
// (находка #3: «соединён, но не подписан» молча терял телеметрию при /ready=200).
|
||||||
|
func Run(ctx context.Context, cfg *config.Config, sqsClient *sqs.Client, log *slog.Logger, onSubscribed func(bool)) error {
|
||||||
log.Info("bridge: starting", "broker", cfg.MQTTBrokerURL, "client_id", cfg.MQTTClientID)
|
log.Info("bridge: starting", "broker", cfg.MQTTBrokerURL, "client_id", cfg.MQTTClientID)
|
||||||
|
|
||||||
// URL очереди получаем один раз при старте (как старый consumer).
|
// URL очереди получаем один раз при старте (как старый consumer).
|
||||||
@@ -46,7 +56,7 @@ func Run(ctx context.Context, cfg *config.Config, sqsClient *sqs.Client, log *sl
|
|||||||
|
|
||||||
handler := newMessageHandler(dispatcher, log)
|
handler := newMessageHandler(dispatcher, log)
|
||||||
|
|
||||||
client, err := connectMQTT(ctx, cfg, log, handler)
|
client, err := connectMQTT(ctx, cfg, log, handler, onSubscribed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dispatcher.closeAndWait()
|
dispatcher.closeAndWait()
|
||||||
return err
|
return err
|
||||||
@@ -70,7 +80,7 @@ func (e errBridge) Error() string { return string(e) }
|
|||||||
|
|
||||||
// connectMQTT устанавливает подключение к EMQX с автореконнектом.
|
// connectMQTT устанавливает подключение к EMQX с автореконнектом.
|
||||||
// handler передаётся в OnConnectHandler: подписка повторяется при каждом подключении.
|
// handler передаётся в OnConnectHandler: подписка повторяется при каждом подключении.
|
||||||
func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, handler mqtt.MessageHandler) (mqtt.Client, error) {
|
func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, handler mqtt.MessageHandler, onSubscribed func(bool)) (mqtt.Client, error) {
|
||||||
opts := mqtt.NewClientOptions()
|
opts := mqtt.NewClientOptions()
|
||||||
opts.AddBroker(cfg.MQTTBrokerURL)
|
opts.AddBroker(cfg.MQTTBrokerURL)
|
||||||
opts.SetClientID(cfg.MQTTClientID)
|
opts.SetClientID(cfg.MQTTClientID)
|
||||||
@@ -84,6 +94,7 @@ func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, hand
|
|||||||
|
|
||||||
opts.SetConnectionLostHandler(func(_ mqtt.Client, err error) {
|
opts.SetConnectionLostHandler(func(_ mqtt.Client, err error) {
|
||||||
log.Warn("bridge: MQTT connection lost, reconnecting...", "err", err)
|
log.Warn("bridge: MQTT connection lost, reconnecting...", "err", err)
|
||||||
|
setSubscribed(onSubscribed, false)
|
||||||
})
|
})
|
||||||
opts.SetReconnectingHandler(func(_ mqtt.Client, _ *mqtt.ClientOptions) {
|
opts.SetReconnectingHandler(func(_ mqtt.Client, _ *mqtt.ClientOptions) {
|
||||||
log.Info("bridge: MQTT reconnecting...")
|
log.Info("bridge: MQTT reconnecting...")
|
||||||
@@ -98,6 +109,7 @@ func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, hand
|
|||||||
token := c.Subscribe(telemetryTopicFilter, 1, handler)
|
token := c.Subscribe(telemetryTopicFilter, 1, handler)
|
||||||
if token.WaitTimeout(10*time.Second) && token.Error() == nil {
|
if token.WaitTimeout(10*time.Second) && token.Error() == nil {
|
||||||
log.Info("bridge: subscribed", "filter", telemetryTopicFilter, "attempt", attempt)
|
log.Info("bridge: subscribed", "filter", telemetryTopicFilter, "attempt", attempt)
|
||||||
|
setSubscribed(onSubscribed, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Error("bridge: subscribe failed", "filter", telemetryTopicFilter, "attempt", attempt, "err", token.Error())
|
log.Error("bridge: subscribe failed", "filter", telemetryTopicFilter, "attempt", attempt, "err", token.Error())
|
||||||
@@ -107,6 +119,9 @@ func connectMQTT(ctx context.Context, cfg *config.Config, log *slog.Logger, hand
|
|||||||
case <-time.After(subscribeRetryInterval):
|
case <-time.After(subscribeRetryInterval):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Подписка не удалась — bridge не готов (/ready=503) до следующего
|
||||||
|
// реконнекта.
|
||||||
|
setSubscribed(onSubscribed, false)
|
||||||
log.Error("bridge: subscribe failed after retries, will retry on next reconnect", "filter", telemetryTopicFilter)
|
log.Error("bridge: subscribe failed after retries, will retry on next reconnect", "filter", telemetryTopicFilter)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user