@@ -1224,6 +1224,7 @@ NodeNum Router::getNodeNum()
12241224
12251225bool Router::enqueueDeferredLocal (meshtastic_MeshPacket *p, RxSource src)
12261226{
1227+ concurrency::LockGuard g (&deferredLock);
12271228 if (deferredLocalCount >= deferredLocalCapacity)
12281229 return false ;
12291230 uint8_t tail = (deferredLocalHead + deferredLocalCount) % deferredLocalCapacity;
@@ -1235,6 +1236,7 @@ bool Router::enqueueDeferredLocal(meshtastic_MeshPacket *p, RxSource src)
12351236
12361237bool Router::dequeueDeferredLocal (DeferredLocal &out)
12371238{
1239+ concurrency::LockGuard g (&deferredLock);
12381240 if (deferredLocalCount == 0 )
12391241 return false ;
12401242 out = deferredLocalQueue[deferredLocalHead];
@@ -1246,7 +1248,12 @@ bool Router::dequeueDeferredLocal(DeferredLocal &out)
12461248void Router::deliverLocal (meshtastic_MeshPacket *p, RxSource src)
12471249{
12481250 // Top level: handle synchronously, exactly as before the depth guard existed.
1249- if (handleDepth == 0 ) {
1251+ bool nested;
1252+ {
1253+ concurrency::LockGuard g (&deferredLock);
1254+ nested = handleDepth > 0 ;
1255+ }
1256+ if (!nested) {
12501257 handleReceived (p, src);
12511258 return ;
12521259 }
@@ -1275,29 +1282,36 @@ void Router::deliverLocal(meshtastic_MeshPacket *p, RxSource src)
12751282 */
12761283void Router::handleReceived (meshtastic_MeshPacket *p, RxSource src)
12771284{
1278- handleDepth++;
1285+ {
1286+ concurrency::LockGuard g (&deferredLock);
1287+ handleDepth++;
12791288#ifdef PIO_UNIT_TESTING
1280- if (handleDepth > maxHandleDepthObserved)
1281- maxHandleDepthObserved = handleDepth;
1289+ if (handleDepth > maxHandleDepthObserved)
1290+ maxHandleDepthObserved = handleDepth;
12821291#endif
1292+ }
12831293
12841294 dispatchReceived (p, src);
12851295
1286- // Only the outermost frame drains. Deferred packets were produced by modules sending from
1287- // inside dispatchReceived()'s callModules(); process them here, after the triggering frame has
1288- // unwound, so a second handleReceived() never sits on top of a module handler. handleDepth
1289- // stays >= 1 through the drain, so a drained packet whose own modules send more loopback
1290- // packets enqueues them for this same loop rather than recursing: the stack stays flat and
1291- // processing is breadth-first.
1292- if (handleDepth == 1 ) {
1296+ // Only the outermost frame drains, after the triggering frame unwound, so a second
1297+ // handleReceived() never sits on a module handler. Depth stays >=1, keeping the drain flat.
1298+ bool outermost;
1299+ {
1300+ concurrency::LockGuard g (&deferredLock);
1301+ outermost = handleDepth == 1 ;
1302+ }
1303+ if (outermost) {
12931304 DeferredLocal d;
12941305 while (dequeueDeferredLocal (d)) {
12951306 dispatchReceived (d.p , d.src );
12961307 packetPool.release (d.p );
12971308 }
12981309 }
12991310
1300- handleDepth--;
1311+ {
1312+ concurrency::LockGuard g (&deferredLock);
1313+ handleDepth--;
1314+ }
13011315}
13021316
13031317void Router::dispatchReceived (meshtastic_MeshPacket *p, RxSource src)
0 commit comments