fix(tci): clear DAX channel→TRX cache on disconnect teardown (#3766)#3767
Conversation
#3759 added the m_channelTrx routing cache and clears it in rearmDaxForProfileLoad() and releaseDaxForTci(), but missed the third teardown path: the connectionStateChanged(false) disconnect handler, which already clears the sibling DAX maps (m_tciDaxStreamIds / m_tciDaxBorrowedChannels / m_tciDaxSlices). Per #3759's own invariant — the routing cache is stale once the channel→stream map is torn down — clear it here too, so a disconnect→ reconnect with a different slice topology can't consult a stale cached slice index for the transient window before the first dax!=0 re-resolves. Closes #3766. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Verified against the source — this is correct and the analysis holds up.
The disconnect handler (connectionStateChanged(false)) already tears down the three sibling DAX maps — m_tciDaxStreamIds, m_tciDaxBorrowedChannels, and m_tciDaxSlices — but not m_channelTrx. The other two teardown paths that exist, rearmDaxForProfileLoad() (TciServer.cpp:2077) and releaseDaxForTci() (TciServer.cpp:2116), both clear m_channelTrx right alongside the sibling clears, so the disconnect path was the lone inconsistency. The new line restores the invariant in the right place — between the m_tciDaxBorrowedChannels.clear() and m_tciDaxSlices.clear(), matching the ordering of the other two sites.
The staleness reasoning checks out: m_channelTrx caches a channel → trx (slice index), consulted at TciServer.cpp:1167 via .value(channel, ...). On a disconnect→reconnect with changed slice topology, a stale index could be read during the transient window before the first dax != 0 re-resolves and overwrites it (TciServer.cpp:1151). Low impact as you note (self-heals, channels bounded), but closing the 2-of-3 gap is the right call.
Minor (optional): the new comment tags (#3766) while the two sibling clears tag (#3669) — the cache's origin. Aligning on #3669 would keep git blame pointing at the same root for all three clears, but it's purely cosmetic.
Scope is a single line in the correct function, no convention concerns. Thanks @ten9876 — nice catch on the inconsistency.
🤖 aethersdr-agent · cost: $2.4242 · model: claude-opus-4-8
Summary
Closes #3766. Adds the missing third cache-clear for
m_channelTrx(the DAX channel→TRX routing cache introduced in #3759).#3759 clears the cache in
rearmDaxForProfileLoad()andreleaseDaxForTci()but not in theconnectionStateChanged(false)disconnect handler — which already tears down the three sibling DAX maps (m_tciDaxStreamIds/m_tciDaxBorrowedChannels/m_tciDaxSlices). Per #3759's own invariant ("routing cache stale once the channel→stream map is torn down"), the disconnect path should clear it too: on a disconnect→reconnect where the slice topology differs, the cachedchannel→trx(a slice index) could be consulted while stale for the transient window before the firstdax!=0re-resolves. Low impact (self-heals; channels bounded 1–4), but it closes the 2-of-3 inconsistency.One line, alongside the existing sibling clears.
Test plan
cmake --build build --target AetherSDR)🤖 Generated with Claude Code