From 6be84eb4eb4bb23acb4770b916499c5cc5bd6856 Mon Sep 17 00:00:00 2001 From: agessaman Date: Tue, 4 Aug 2026 14:03:21 -0700 Subject: [PATCH] fix(mqtt): let a retried setup consume the reconnect allowance reconnect_attempted_this_cycle is computed once before the maintenance loop and only maintainSlotConnection() was setting it. The deferred-setup retry armed the 15 s cross-slot guard via _last_slot_reconnect_ms but left the local flag false, so a disconnected slot later in the same pass still saw "no reconnect yet" and started a second TLS handshake. Two concurrent ~40 KB sessions is exactly the contention the guard prevents, and it landed in the one situation where internal heap is already known to be short -- a failed allocation is why the retry runs. Set on success only: setupSlot() returns true only after client->connect(), so success means a handshake was launched. A failed retry launches nothing and continues to spend only setup_retry_this_cycle, which rate-limits the allocation attempts without consuming the handshake allowance. --- src/helpers/bridges/MQTTBridge.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 6fc26693..90ffbf71 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -2047,7 +2047,17 @@ void MQTTBridge::maintainSlotConnections() { setup_retry_this_cycle = true; MQTT_DEBUG_PRINTLN("MQTT%d retrying deferred setup (int_heap=%d)", i + 1, (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); - if (setupSlot(i)) _last_slot_reconnect_ms = now_millis; + if (setupSlot(i)) { + // A successful setup ends in connect(), so it spends this cycle's single + // handshake allowance as well as arming the 15 s cross-slot guard. Without + // the local flag, a disconnected slot later in this same pass would start a + // second concurrent TLS handshake — the contention the guard exists to + // prevent, and most damaging here because a failed allocation is why we are + // retrying at all. A failed setup launches nothing and so spends only + // setup_retry_this_cycle. + _last_slot_reconnect_ms = now_millis; + reconnect_attempted_this_cycle = true; + } } continue; }