Skip to content

Commit 390e6ed

Browse files
committed
Close the drain and enqueue race on the deferred queue
1 parent 7505fd9 commit 390e6ed

2 files changed

Lines changed: 32 additions & 6 deletions

File tree

src/mesh/Router.cpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,7 +1227,6 @@ NodeNum Router::getNodeNum()
12271227

12281228
bool Router::enqueueDeferredLocal(meshtastic_MeshPacket *p, RxSource src)
12291229
{
1230-
concurrency::LockGuard g(&deferredLock);
12311230
if (deferredLocalCount >= deferredLocalCapacity)
12321231
return false;
12331232
uint8_t tail = (deferredLocalHead + deferredLocalCount) % deferredLocalCapacity;
@@ -1239,7 +1238,6 @@ bool Router::enqueueDeferredLocal(meshtastic_MeshPacket *p, RxSource src)
12391238

12401239
bool Router::dequeueDeferredLocal(DeferredLocal &out)
12411240
{
1242-
concurrency::LockGuard g(&deferredLock);
12431241
if (deferredLocalCount == 0)
12441242
return false;
12451243
out = deferredLocalQueue[deferredLocalHead];
@@ -1265,8 +1263,25 @@ void Router::deliverLocal(meshtastic_MeshPacket *p, RxSource src)
12651263
// handleReceived() drains it once the current dispatch unwinds, instead of stacking another
12661264
// handleReceived() frame on top of the module handler (nRF52 stack overflow on config save).
12671265
meshtastic_MeshPacket *copy = packetPool.allocCopy(*p);
1268-
if (copy && enqueueDeferredLocal(copy, src))
1269-
return;
1266+
if (copy) {
1267+
// Re-check depth under the lock that also gates the drain's decrement, so a drain finishing
1268+
// while we allocated cannot leave this copy stranded in the ring.
1269+
bool stillNested = false, queued = false;
1270+
{
1271+
concurrency::LockGuard g(&deferredLock);
1272+
stillNested = handleDepth > 0;
1273+
if (stillNested)
1274+
queued = enqueueDeferredLocal(copy, src);
1275+
}
1276+
if (queued)
1277+
return;
1278+
if (!stillNested) {
1279+
// The drain finished first, so nothing would pick this up; deliver it here instead.
1280+
dispatchReceived(copy, src);
1281+
packetPool.release(copy);
1282+
return;
1283+
}
1284+
}
12701285

12711286
// Pool exhausted or queue full: drop the deferral. Leak-free and degraded but safe - the
12721287
// packet still followed its normal non-loopback path (SHOULD_RELEASE, or the TX path for a
@@ -1304,8 +1319,17 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
13041319
outermost = handleDepth == 1;
13051320
}
13061321
if (outermost) {
1307-
DeferredLocal d;
1308-
while (dequeueDeferredLocal(d)) {
1322+
// Drop to zero only while the ring is observed empty under the lock, so an enqueuer that
1323+
// re-checks depth either lands in the ring we still drain, or sees 0 and delivers itself.
1324+
for (;;) {
1325+
DeferredLocal d;
1326+
{
1327+
concurrency::LockGuard g(&deferredLock);
1328+
if (!dequeueDeferredLocal(d)) {
1329+
handleDepth--;
1330+
return;
1331+
}
1332+
}
13091333
dispatchReceived(d.p, d.src);
13101334
packetPool.release(d.p);
13111335
}

src/mesh/Router.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,10 @@ class Router : protected concurrency::OSThread, protected PacketHistory
215215
uint8_t deferredLocalCount = 0; // entries currently queued
216216

217217
/// Queue a deferred local packet. Returns false (and queues nothing) when full.
218+
/// Caller must hold deferredLock: the enqueue decision is atomic with the drain's depth update.
218219
bool enqueueDeferredLocal(meshtastic_MeshPacket *p, RxSource src);
219220
/// Pop the oldest deferred local packet into out. Returns false when empty.
221+
/// Caller must hold deferredLock.
220222
bool dequeueDeferredLocal(DeferredLocal &out);
221223

222224
/** Frees the provided packet, and generates a NAK indicating the specifed error while sending */

0 commit comments

Comments
 (0)