Retry 3 times with a 200ms,300ms,400ms backoff.
This commit is contained in:
1 parent
2af7b75d70
commit
dbc4dda0ca
7 files changed
+236
-13
No files matched your search
@@ -161,6 +161,8 @@ protected:
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
virtual int getAGCResetInterval() const { return 0; } // disabled by default
|
||||
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
|
||||
const Packet* getOutboundInFlight() const { return outbound; }
|
||||
|
||||
public:
|
||||
void begin();
|
||||
|
||||
+53
-2
@@ -3,12 +3,16 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS = 3;
|
||||
static const uint32_t DIRECT_RETRY_BACKOFF_MS[DIRECT_RETRY_MAX_ATTEMPTS] = { 200, 300, 400 };
|
||||
|
||||
void Mesh::begin() {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].retry_attempts_sent = 0;
|
||||
_direct_retries[i].priority = 0;
|
||||
_direct_retries[i].progress_marker = 0;
|
||||
_direct_retries[i].expect_path_growth = false;
|
||||
@@ -27,6 +31,9 @@ void Mesh::loop() {
|
||||
}
|
||||
|
||||
if (!isDirectRetryQueued(_direct_retries[i].packet)) {
|
||||
if (_direct_retries[i].packet == getOutboundInFlight()) {
|
||||
continue; // currently transmitting; keep slot until onSendComplete/onSendFail emits event
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -443,6 +450,7 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].retry_attempts_sent = 0;
|
||||
_direct_retries[idx].priority = 0;
|
||||
_direct_retries[idx].progress_marker = 0;
|
||||
_direct_retries[idx].expect_path_growth = false;
|
||||
@@ -491,8 +499,12 @@ bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
onDirectRetryEvent("canceled_echo", _direct_retries[i].packet, 0);
|
||||
onDirectRetryEvent("good", _direct_retries[i].packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
} else {
|
||||
onDirectRetryEvent("canceled_echo", _direct_retries[i].trigger_packet, 0);
|
||||
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
cleared = true;
|
||||
@@ -510,7 +522,35 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The retry packet itself just finished transmitting; Dispatcher will release it after this hook.
|
||||
clearDirectRetrySlot(i);
|
||||
onDirectRetryEvent("resent", packet, 0);
|
||||
_direct_retries[i].retry_attempts_sent++;
|
||||
if (_direct_retries[i].retry_attempts_sent >= DIRECT_RETRY_MAX_ATTEMPTS) {
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
*retry = *packet;
|
||||
uint32_t retry_delay = DIRECT_RETRY_BACKOFF_MS[_direct_retries[i].retry_attempts_sent];
|
||||
sendPacket(retry, _direct_retries[i].priority, retry_delay);
|
||||
if (isDirectRetryQueued(retry)) {
|
||||
_direct_retries[i].packet = retry;
|
||||
_direct_retries[i].retry_delay = retry_delay;
|
||||
_direct_retries[i].retry_at = futureMillis(retry_delay);
|
||||
onDirectRetryEvent("queued", retry, retry_delay);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -522,6 +562,8 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
// Allocate the retry packet only after TX-complete so busy repeaters do not reserve pool slots early.
|
||||
Packet* retry = obtainNewPacket();
|
||||
if (retry == NULL) {
|
||||
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
continue;
|
||||
}
|
||||
@@ -535,7 +577,10 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].queued = true;
|
||||
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay);
|
||||
} else {
|
||||
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay);
|
||||
onDirectRetryEvent("failure", retry, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -550,12 +595,16 @@ void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
if (_direct_retries[i].queued) {
|
||||
if (_direct_retries[i].packet == packet) {
|
||||
// The queued retry itself failed; Dispatcher will release it after this hook.
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
onDirectRetryEvent("dropped_send_fail", packet, 0);
|
||||
onDirectRetryEvent("failure", packet, 0);
|
||||
clearDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
@@ -638,17 +687,19 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
|
||||
}
|
||||
|
||||
// Only store retry metadata here; allocate the retry packet after the initial TX really completes.
|
||||
uint32_t retry_delay = getDirectRetryEchoDelay(packet);
|
||||
uint32_t retry_delay = DIRECT_RETRY_BACKOFF_MS[0];
|
||||
calculateDirectRetryKey(packet, _direct_retries[slot_idx].retry_key);
|
||||
_direct_retries[slot_idx].packet = NULL;
|
||||
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].retry_attempts_sent = 0;
|
||||
_direct_retries[slot_idx].priority = priority;
|
||||
_direct_retries[slot_idx].progress_marker = progress_marker;
|
||||
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
onDirectRetryEvent("armed", packet, retry_delay);
|
||||
}
|
||||
|
||||
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
|
||||
|
||||
@@ -34,6 +34,7 @@ class Mesh : public Dispatcher {
|
||||
Packet* trigger_packet;
|
||||
unsigned long retry_at;
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t priority;
|
||||
uint8_t progress_marker;
|
||||
@@ -111,6 +112,11 @@ protected:
|
||||
*/
|
||||
virtual uint8_t getExtraAckTransmitCount() const;
|
||||
|
||||
/**
|
||||
* \brief Optional hook for logging direct-retry lifecycle events.
|
||||
*/
|
||||
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis) { }
|
||||
|
||||
/**
|
||||
* \brief Perform search of local DB of peers/contacts.
|
||||
* \returns Number of peers with matching hash
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
public:
|
||||
typedef bool (*RecentRepeaterAllowFn)(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
|
||||
|
||||
struct RecentRepeaterInfo {
|
||||
// Just enough identity to match a next-hop path prefix plus the SNR that heard it.
|
||||
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
|
||||
@@ -28,6 +30,9 @@ private:
|
||||
uint32_t _direct_dups, _flood_dups;
|
||||
RecentRepeaterInfo _recent_repeaters[MAX_RECENT_REPEATERS];
|
||||
int _next_recent_repeater_idx;
|
||||
int8_t _recent_repeater_min_snr_x4;
|
||||
RecentRepeaterAllowFn _recent_repeater_allow_fn;
|
||||
void* _recent_repeater_allow_ctx;
|
||||
|
||||
bool hasSeenAck(uint32_t ack) const {
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
@@ -58,6 +63,11 @@ private:
|
||||
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
|
||||
}
|
||||
|
||||
bool prefixesOverlap(const uint8_t* a, uint8_t a_len, const uint8_t* b, uint8_t b_len) const {
|
||||
uint8_t n = a_len < b_len ? a_len : b_len;
|
||||
return n > 0 && memcmp(a, b, n) == 0;
|
||||
}
|
||||
|
||||
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
|
||||
// Learn repeater prefixes only from packet shapes that expose a trustworthy repeater ID.
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->payload_len >= PUB_KEY_SIZE) {
|
||||
@@ -96,14 +106,10 @@ private:
|
||||
if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = packet->_snr;
|
||||
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
if (packet->_snr < _recent_repeater_min_snr_x4) {
|
||||
return;
|
||||
}
|
||||
setRecentRepeater(prefix, prefix_len, packet->_snr);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -115,6 +121,9 @@ public:
|
||||
_direct_dups = _flood_dups = 0;
|
||||
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
|
||||
_next_recent_repeater_idx = 0;
|
||||
_recent_repeater_min_snr_x4 = -128;
|
||||
_recent_repeater_allow_fn = NULL;
|
||||
_recent_repeater_allow_ctx = NULL;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
@@ -216,19 +225,74 @@ public:
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
void setRecentRepeaterMinSNRX4(int8_t min_snr_x4) {
|
||||
_recent_repeater_min_snr_x4 = min_snr_x4;
|
||||
}
|
||||
void setRecentRepeaterAllowFilter(RecentRepeaterAllowFn fn, void* ctx) {
|
||||
_recent_repeater_allow_fn = fn;
|
||||
_recent_repeater_allow_ctx = ctx;
|
||||
}
|
||||
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4) {
|
||||
if (prefix == NULL || prefix_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
prefix_len = MAX_ROUTE_HASH_BYTES;
|
||||
}
|
||||
|
||||
if (_recent_repeater_allow_fn != NULL && !_recent_repeater_allow_fn(prefix, prefix_len, _recent_repeater_allow_ctx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep one slot for overlapping prefixes so 1/2/3-byte paths share the same entry.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
RecentRepeaterInfo& existing = _recent_repeaters[i];
|
||||
if (existing.prefix_len == 0 || !prefixesOverlap(existing.prefix, existing.prefix_len, prefix, prefix_len)) {
|
||||
continue;
|
||||
}
|
||||
if (prefix_len > existing.prefix_len) {
|
||||
memset(existing.prefix, 0, sizeof(existing.prefix));
|
||||
memcpy(existing.prefix, prefix, prefix_len);
|
||||
existing.prefix_len = prefix_len;
|
||||
}
|
||||
existing.snr_x4 = snr_x4;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
|
||||
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
|
||||
memset(slot.prefix, 0, sizeof(slot.prefix));
|
||||
memcpy(slot.prefix, prefix, prefix_len);
|
||||
slot.prefix_len = prefix_len;
|
||||
slot.snr_x4 = snr_x4;
|
||||
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
|
||||
return true;
|
||||
}
|
||||
const RecentRepeaterInfo* getLatestRecentRepeater() const {
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len > 0) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const {
|
||||
if (hash == NULL || hash_len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search newest-to-oldest so the retry gate prefers the freshest SNR sample for a prefix.
|
||||
// Search newest-to-oldest and allow 1/2/3-byte prefixes to overlap-match.
|
||||
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
|
||||
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
|
||||
const RecentRepeaterInfo* info = &_recent_repeaters[idx];
|
||||
if (info->prefix_len < hash_len || info->prefix_len == 0) {
|
||||
if (info->prefix_len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(info->prefix, hash, hash_len) == 0) {
|
||||
if (prefixesOverlap(info->prefix, info->prefix_len, hash, hash_len)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user