@@ -1227,7 +1227,6 @@ NodeNum Router::getNodeNum()
12271227
12281228bool 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
12401239bool 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 }
0 commit comments