@@ -1227,6 +1227,7 @@ NodeNum Router::getNodeNum()
12271227
12281228bool Router::enqueueDeferredLocal (meshtastic_MeshPacket *p, RxSource src)
12291229{
1230+ concurrency::LockGuard g (&deferredLock);
12301231 if (deferredLocalCount >= deferredLocalCapacity)
12311232 return false ;
12321233 uint8_t tail = (deferredLocalHead + deferredLocalCount) % deferredLocalCapacity;
@@ -1238,6 +1239,7 @@ bool Router::enqueueDeferredLocal(meshtastic_MeshPacket *p, RxSource src)
12381239
12391240bool Router::dequeueDeferredLocal (DeferredLocal &out)
12401241{
1242+ concurrency::LockGuard g (&deferredLock);
12411243 if (deferredLocalCount == 0 )
12421244 return false ;
12431245 out = deferredLocalQueue[deferredLocalHead];
@@ -1249,7 +1251,12 @@ bool Router::dequeueDeferredLocal(DeferredLocal &out)
12491251void Router::deliverLocal (meshtastic_MeshPacket *p, RxSource src)
12501252{
12511253 // Top level: handle synchronously, exactly as before the depth guard existed.
1252- if (handleDepth == 0 ) {
1254+ bool nested;
1255+ {
1256+ concurrency::LockGuard g (&deferredLock);
1257+ nested = handleDepth > 0 ;
1258+ }
1259+ if (!nested) {
12531260 handleReceived (p, src);
12541261 return ;
12551262 }
@@ -1278,29 +1285,36 @@ void Router::deliverLocal(meshtastic_MeshPacket *p, RxSource src)
12781285 */
12791286void Router::handleReceived (meshtastic_MeshPacket *p, RxSource src)
12801287{
1281- handleDepth++;
1288+ {
1289+ concurrency::LockGuard g (&deferredLock);
1290+ handleDepth++;
12821291#ifdef PIO_UNIT_TESTING
1283- if (handleDepth > maxHandleDepthObserved)
1284- maxHandleDepthObserved = handleDepth;
1292+ if (handleDepth > maxHandleDepthObserved)
1293+ maxHandleDepthObserved = handleDepth;
12851294#endif
1295+ }
12861296
12871297 dispatchReceived (p, src);
12881298
1289- // Only the outermost frame drains. Deferred packets were produced by modules sending from
1290- // inside dispatchReceived()'s callModules(); process them here, after the triggering frame has
1291- // unwound, so a second handleReceived() never sits on top of a module handler. handleDepth
1292- // stays >= 1 through the drain, so a drained packet whose own modules send more loopback
1293- // packets enqueues them for this same loop rather than recursing: the stack stays flat and
1294- // processing is breadth-first.
1295- if (handleDepth == 1 ) {
1299+ // Only the outermost frame drains, after the triggering frame unwound, so a second
1300+ // handleReceived() never sits on a module handler. Depth stays >=1, keeping the drain flat.
1301+ bool outermost;
1302+ {
1303+ concurrency::LockGuard g (&deferredLock);
1304+ outermost = handleDepth == 1 ;
1305+ }
1306+ if (outermost) {
12961307 DeferredLocal d;
12971308 while (dequeueDeferredLocal (d)) {
12981309 dispatchReceived (d.p , d.src );
12991310 packetPool.release (d.p );
13001311 }
13011312 }
13021313
1303- handleDepth--;
1314+ {
1315+ concurrency::LockGuard g (&deferredLock);
1316+ handleDepth--;
1317+ }
13041318}
13051319
13061320void Router::dispatchReceived (meshtastic_MeshPacket *p, RxSource src)
0 commit comments