Repository navigation
Fix circuit lifetime, lock ordering and cross-thread stat races in threaded UDP receive - #6404
akleshchev with Copilot wants to merge 7 commits into
Conversation
Co-authored-by: akleshchev <117672381+akleshchev@users.noreply.github.com>
Co-authored-by: akleshchev <117672381+akleshchev@users.noreply.github.com>
Co-authored-by: akleshchev <117672381+akleshchev@users.noreply.github.com>
Co-authored-by: akleshchev <117672381+akleshchev@users.noreply.github.com>
|
@copilot these LLCircuit changes look excessive for the purpose of removal alone. Is there a simpler solution? Perhaps there can be some LLCircuir graveyard that gets cleaned by the UDP thread in some fassion? removeCircuitData appears to be strictly main thread, so it's only UDP that has to worry about circuit dying. |
Co-authored-by: akleshchev <117672381+akleshchev@users.noreply.github.com>
Switched to the graveyard approach in 6d4b4b4 — How it works now:
The only other change left in |
Description
Follow-up to review feedback on the threaded UDP receive path (findings 1, 5, 6, 7, 8, 9). The receiver thread was using a raw
LLCircuitData*that the main thread could delete mid-decode, acquiredmCircuitMutex/mDataMutexin inconsistent orders, and mutated message-template counters, decode-timing stats and packet-drop simulation state without synchronization.Circuit lifetime (1)
LLCircuitDataderives fromstd::enable_shared_from_this;LLCircuit::circuit_data_mapholdsstd::shared_ptr.LLCircuit::findCircuitRef()returns a pinned reference.decodeDataOwned()uses it, so the circuit stays alive across validation, ACK bookkeeping, duplicate handling and accounting.findCircuit()remains as a raw-pointer wrapper for main-thread callers.removeCircuitData()erases undermCircuitMutex, parks still-referenced circuits inmRetiredCircuits, and destroys circuits only after releasing the mutex;purgeRetiredCircuits()reclaims them.updateWatchDogTimers(),resendUnackedPackets()andisCircuitAlive()were converted to match.Lock ordering (5)
llcircuit.h:LLCircuit::mCircuitMutex→LLCircuitData::mDataMutex, never inverted, and no callbacks or sends under either lock.collectRAck()was inverted and now takesmCircuitMutexfirst.sendAcks()swaps each circuit's pending ACKs out while both locks are held (so acks collected afterwards re-register the circuit), then sends with no lock held, holding ashared_ptrto keep the circuit alive.Template counters and timing stats (6, 7)
mReceiveCount,mReceiveBytes,mReceiveInvalid,mTotalDecoded,mTotalDecodeTime,mMaxDecodeTimePerMsgare now atomic and private, reached throughrecordReceive(),recordReceiveCount(),resetReceiveCounts(),recordDecodeTime(),resetDecodeStats()and getters. Float add/max use CAS loops.LLMessageTemplategets explicit copy ctor/assignment, since atomics delete the implicit ones and existing tests copy templates.Packet-drop simulation (8)
mDropPercentageandmPacketsToDropare atomic;computeDrop()consumes a pending drop via CAS. A percentage hit still does not consume a requested drop (the old code incremented then immediately decremented the counter).Invalid-circuit logging (9)
logMsgFromInvalidCircuit(host, recv_reliable)instead of passingpkt.getPacketIDChecked(); other logging call sites were checked for the correct reliability value.Review items 2, 3, 4 and 10 are untouched.
Related Issues
Checklist
Please ensure the following before requesting review:
Additional Notes
Adds
indra/llmessage/tests/llmessagetemplate_test.cpp(registered inCMakeLists.txt) covering counter/timing accumulation and reset, concurrent updates from four threads, and the new explicit copy operations.Verification was limited to
g++ -fsyntax-onlyon the touchedllmessagetranslation units and a standalone harness exercising the atomic add/max helpers andcomputeDrop()semantics under contention — a full autobuild and the tut integration tests could not be run in this environment, so the new test still needs to be executed in CI.