fix(database): make DB updates atomic across device switches#6256
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis change makes radio processing, handshake state, device association, database writes, and shutdown session-aware. It adds immutable received frames, stale-session rejection, atomic DAO operations, writer admission gates, pending-route recovery, logical retirement, and suspendable database shutdown. ChangesSession-bound radio and handshake flow
Atomic database operations
Database lifecycle
Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This is strong work — I went through it in detail. The transactional consolidation checks out: the new Four issues to resolve before this leaves draft, roughly in severity order: 1. The 2. The routing-metadata failure path doesn't self-repair the way its comment says. If the datastore alias write fails after a committed merge, the comment claims the next connect repairs it via the already-unified branch — but a real reconnect resolves the address through the 3. The FTS backfill writes outside the barrier. 4. Two nits: the PR body says mesh-log cleanup is atomic, but the protobuf-parsing selection runs outside the transaction (the code comment acknowledges this — the body overstates it); and the On the retirement lifecycle: accumulation is bounded (one retired instance per merge event, realistically 1–2 per process) and process death is safe since SQLite recovers the WAL, so deferring physical close to shutdown is the right call. The one cost is retired DB files persisting on disk across process death until a later process's eviction — fine, but worth a line in the KDoc. |
|
@jamesarich Thanks for the detailed review. I agree with all four findings. I’m going to make the retry path reacquire admission through For routing recovery, I’ll make the merge final an active association gate. For routing recovery, I’ll make the merge finalization two-phase and crash-recoverable: persist a pending source/destination route before committing the merge, verify the destination’s merge marker during a later address switch when the final alias is missing, and repair the alias before publishing either database. A pre-commit failure will leave no valid marker and safely fall back to the source. The FTS backfill will enter the same writer-admission accounting as other writes and skip its captured database if the admitted active database has changed. I’ll also make gate release idempotent and I’ll add deterministic coverage for each path and correct the mesh-log wording, connection-pool KDoc, and process-death cleanup note before moving the PR out of draft. |
2bd21e8 to
1eda08e
Compare
|
I reworked this and updated it against current The issues you called out are addressed:
I also tightened the rollback, recovery, shutdown, reconnect, and stale-frame coverage. Exact-head CI is green. |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt (1)
172-178: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick winResolve the protected database after acquiring the manager mutex.
Deferred enforcement can wait behind a later switch, leaving
activeDbNamestale. The new active database may then be selected, closed, and deleted while_currentDbstill references it.Proposed fix
- enforceCacheLimit(activeDbName = currentDbName) + enforceCacheLimit() ... - launchManagerWork(dispatchers.io) { enforceCacheLimit(activeDbName = dbName) } + launchManagerWork(dispatchers.io) { enforceCacheLimit() } ... - launchManagerWork(dispatchers.io) { enforceCacheLimit(activeDbName = currentDbName) } + launchManagerWork(dispatchers.io) { enforceCacheLimit() } ... - private suspend fun enforceCacheLimit(activeDbName: String) = withManagerOperation { + private suspend fun enforceCacheLimit() = withManagerOperation { mutex.withLock { + val activeDbName = currentDbName val limit = getCurrentCacheLimit()Also applies to: 437-449, 885-887, 1121-1135
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt` around lines 172 - 178, Update setCacheLimit and the other affected enforcement paths to resolve the protected database only after entering the manager mutex via launchManagerWork. Read the current active database name and acquire/protect that database inside the serialized block immediately before enforceCacheLimit, rather than capturing currentDbName when scheduling deferred work, so enforcement applies to the database active at execution time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt`:
- Around line 255-264: The session re-check in handleMyInfo must occur before
handshakeState, setMyDeviceId, and setMyNodeNum are updated. Move the existing
isActiveSession(session) guard ahead of these identity writes so a session
change prevents all stale mutations, and add a regression test that flips
activeSession between admission checks and verifies no state is written.
In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.kt`:
- Line 58: Replace testScope.runTest with top-level
runTest(testScope.coroutineContext) in all affected tests:
NodeManagerConnectionIdentityTest.kt lines 58, 77, 92, 109, 170, and 186.
Preserve each test body and existing testScope context.
In
`@core/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.kt`:
- Around line 112-119: Update the session-generation collector in
RadioControllerImpl so clearing connection identity is generation-aware and
cannot remove an identity already published for the new generation; only clear
when the stored identity does not match generation. Add a deterministic
regression test that publishes the new identity before the boundary collector
executes and verifies it is preserved.
In
`@core/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.kt`:
- Around line 578-591: Add failure-safe rollback around transport creation in
the session admission flow using activeTransportSession, _activeSession,
_sessionGeneration, isStarted, runningTransportId, and radioTransport. If
transportFactory.createTransport throws, clear the admitted session and
transport state, reset the lifecycle fields to their inactive values, then
rethrow the original exception.
In
`@core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.kt`:
- Around line 59-68: The fake service currently creates sessions from
setDeviceAddress and never advances _sessionGeneration. Update setDeviceAddress
to only update the selected device, and move session creation plus generation
increments into connect() and restartTransport(), ensuring every
connect/restart—including same-address reconnects—gets a fresh generation and no
session exists before connect.
---
Outside diff comments:
In
`@core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt`:
- Around line 172-178: Update setCacheLimit and the other affected enforcement
paths to resolve the protected database only after entering the manager mutex
via launchManagerWork. Read the current active database name and acquire/protect
that database inside the serialized block immediately before enforceCacheLimit,
rather than capturing currentDbName when scheduling deferred work, so
enforcement applies to the database active at execution time.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 28cdbc3b-674e-4a23-99df-ff8b2049af33
📒 Files selected for processing (39)
androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.ktcore/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.ktcore/database/build.gradle.ktscore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.ktcore/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/RadioInterfaceService.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.ktdesktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
1eda08e to
2350f49
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt (1)
153-179: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftSanitize audit records before persisting them.
logVariantstoresNodeInfo.toPIIString()and complete configuration/channel representations inMeshLog, potentially retaining identity, location, or key material. Persist redacted summaries and sanitize or omit the storedfromRadiopayload for sensitive variants.As per coding guidelines, Kotlin code must never log or expose PII, location data, or cryptographic keys. As per path instructions, flag any such logging in
**/*.kt.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt` around lines 153 - 179, The logVariant method persists sensitive NodeInfo, configuration, channel, and fromRadio data in MeshLog. Replace PII-bearing and complete representations with redacted summaries, and sanitize or omit the fromRadio payload for those sensitive variants; ensure no identity, location, or cryptographic key material is stored while preserving safe logging for non-sensitive variants.Sources: Coding guidelines, Path instructions
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt (1)
124-173: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftRetain session authority for deferred packet work.
sessionis only carried to the ADMIN branch. Text/reaction persistence, ACK updates, telemetry, and other detachedscopejobs can begin after the originating lease ends and write against a newly associated device database. Thread the session into those asynchronous paths and enterrunWhileSessionActive(session)before side effects; add a reconnect-generation regression test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt` around lines 124 - 173, Preserve the originating session for all deferred packet work, not only the ADMIN branch. Thread session through handleTextMessage, reaction/ACK persistence, telemetry, specialized handlers, and detached scope jobs, invoking runWhileSessionActive(session) before database or other side effects. Add a regression test covering reconnect generation to verify stale deferred work cannot write through a newly associated device database.
🧹 Nitpick comments (2)
core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt (1)
152-162: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDeclare transformation callbacks side-effect-free.
NodeManagerImplLines 242–249 can evaluatetransformrepeatedly after CAS contention, but this public contract does not warn callers. Side-effecting callbacks could therefore execute more than once. Document and audit the purity requirement, or serialize callback evaluation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt` around lines 152 - 162, Update the public contracts for updateNode and updateNodeAndPersist to explicitly require side-effect-free transformation callbacks because NodeManagerImpl may reevaluate them after CAS contention. Audit both implementations and call sites for callback side effects, preserving repeated evaluation behavior rather than serializing callbacks unless required by existing semantics.core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.kt (1)
79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
await()here instead ofgetCompleted().await()is the stable equivalent and matches the rest of these tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.kt` at line 79, Update the assertion in DatabaseManagerWriterAdmissionTest to call await() on newWriterDb instead of getCompleted(), preserving the existing expected dbA value and assertion message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt`:
- Around line 303-319: The test around laterPersistence and releaseFirstClear
only verifies that the first clear completed before later persistence began;
strengthen it to assert that all clearLocalConfig, clearLocalModuleConfig,
clearDeviceUIConfig, clearFileManifest, and clearLoraRegionPresetMap operations
finish before laterPersistence starts. Use completion signals or equivalent
ordering verification for each clear, while preserving the existing
blocked-first-clear and later-persistence flow.
In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt`:
- Around line 103-133: The test same-node persistence cannot finish with an
older snapshot currently cannot detect concurrent entry into the first upsert.
Track each nodeRepository.upsert invocation separately, assert the second
invocation has not started while the first is blocked, then release the first
and verify persisted order is [1, 2] along with the final in-memory node state.
In
`@core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.kt`:
- Around line 81-83: Update the transaction flow around DatabaseMerger’s
ensureAssociationActive and immediateTransaction so the association-generation
lease remains held through transaction commit, preventing rollover from
occurring between the authority check and commit. Coordinate session rollover
using the same synchronization primitive, and release the lease only after the
transaction completes or rolls back.
In
`@core/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.kt`:
- Around line 274-297: Update rollbackDeviceSwitch so transport and related
device-state restoration only proceed after
switchActiveDatabase(previousAddress) succeeds; if database rollback fails,
clear the transport selection, keep writes disabled, and propagate or surface
the rollback failure instead of merely suppressing it. Preserve suppression for
independent rollback failures where safe, and add a test covering database
rollback failure and the resulting fail-closed transport behavior.
---
Outside diff comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt`:
- Around line 124-173: Preserve the originating session for all deferred packet
work, not only the ADMIN branch. Thread session through handleTextMessage,
reaction/ACK persistence, telemetry, specialized handlers, and detached scope
jobs, invoking runWhileSessionActive(session) before database or other side
effects. Add a regression test covering reconnect generation to verify stale
deferred work cannot write through a newly associated device database.
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt`:
- Around line 153-179: The logVariant method persists sensitive NodeInfo,
configuration, channel, and fromRadio data in MeshLog. Replace PII-bearing and
complete representations with redacted summaries, and sanitize or omit the
fromRadio payload for those sensitive variants; ensure no identity, location, or
cryptographic key material is stored while preserving safe logging for
non-sensitive variants.
---
Nitpick comments:
In
`@core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.kt`:
- Line 79: Update the assertion in DatabaseManagerWriterAdmissionTest to call
await() on newWriterDb instead of getCompleted(), preserving the existing
expected dbA value and assertion message.
In
`@core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt`:
- Around line 152-162: Update the public contracts for updateNode and
updateNodeAndPersist to explicitly require side-effect-free transformation
callbacks because NodeManagerImpl may reevaluate them after CAS contention.
Audit both implementations and call sites for callback side effects, preserving
repeated evaluation behavior rather than serializing callbacks unless required
by existing semantics.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b552890d-7f48-41ce-bcca-ce51a063cd54
📒 Files selected for processing (62)
androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.ktcore/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.ktcore/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/AirQualityChartReproTest.ktcore/database/build.gradle.ktscore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseProvider.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/SwitchingDiscoveryDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationRecoveryTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerBackfillTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerRetirementTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerShutdownTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerTestFixture.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.ktcore/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AdminPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/RadioInterfaceService.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.ktcore/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKMeshIntegrationTest.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceServiceSessionTest.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/RepositoryFakesTest.ktdesktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
🚧 Files skipped from review as they are similar to previous changes (20)
- core/database/build.gradle.kts
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.kt
- core/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
- core/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.kt
- desktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
- androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.kt
- core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt
2350f49 to
8567039
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt (1)
64-101: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUnbounded
logInsertJob?.join()inside a session lease.
RadioSessionAuthority.runWithSessionLeasedocuments that callers "must keep the block bounded," since teardown waits for this block to finish before completing session teardown / starting a replacement transport.logInsertJob?.join()here has no timeout — if the mesh-log insert stalls (e.g. under DB writer-admission pressure), this session-lease block never returns, and session teardown/reconnect blocks indefinitely on it.🐛 Proposed fix: bound the join with a timeout
- if (logUuid != null) { - logInsertJob?.join() + if (logUuid != null) { + withTimeoutOrNull(LOG_INSERT_JOIN_TIMEOUT) { logInsertJob?.join() } val routeNodeNums = (forwardRoute + returnRoute).distinct()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt` around lines 64 - 101, Bound the logInsertJob wait inside the radioInterfaceService.launchSessionWork session-lease block: replace the unbounded join in TracerouteHandlerImpl’s traceroute handling with a finite timeout, while preserving the existing snapshot update flow after the wait. Ensure a stalled mesh-log insert cannot prevent the lease block from returning and session teardown or transport replacement from proceeding.
🧹 Nitpick comments (2)
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt (1)
58-230: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMissing coverage for the new session-rejection guard in two handlers.
This PR adds session-admission gating so stale-session work is skipped (
runWithSessionLease/runWhileSessionActivereturningfalse).StoreForwardPacketHandlerImplTest.kt,NodeManagerImplTest.kt, andMeshDataHandlerTest.ktall added a dedicated "retired/rejected session" test for this guard, but two sibling suites in this same cohort don't:
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt#L58-L230: add a test that stubsradioInterfaceService.runWithSessionLease(radioSession, any())to returnfalseand assertsnodeManager.updateNodeForSession(...)is never reached, mirroring the pattern already used forStoreForwardPacketHandlerImplTest.kt's SFPP rejection test.core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt#L107-L133: add a test that callsonPositionReceived(...)with a non-nullRadioSessionContextwhile the mockedRadioInterfaceServicereports that session as inactive, and assertevaluate(...)/notifications never fire, to exercise the new session-gated branch added inGeofenceMonitor.kt.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt` around lines 58 - 230, Add rejected-session coverage in TelemetryPacketHandlerImplTest.kt (lines 58-230) by stubbing radioInterfaceService.runWithSessionLease for radioSession to return false, invoking handleTelemetry, and asserting nodeManager.updateNodeForSession is never called. Also update GeofenceMonitorTest.kt (lines 107-133) with a non-null RadioSessionContext whose session is reported inactive, then call onPositionReceived and verify evaluate and notifications do not run.core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt (1)
61-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared telemetry update helper.
TelemetryPacketHandlerImplandNodeManagerImpl.handleReceivedTelemetryboth apply the same metrics copies andlastHeardclamping; move that logic into a shared private helper and call it from both paths so the behavior stays aligned.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt` around lines 61 - 88, Extract the duplicated telemetry-to-Node transformation from handleTelemetry and NodeManagerImpl.handleReceivedTelemetry into one shared private helper. The helper must copy all telemetry metric fields and update lastHeard using the existing telemetry-time fallback and clampTimestampToNow/maxOf logic, then replace both inline implementations with calls to it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.kt`:
- Line 23: Update the Room import in DatabaseManagerPendingRouteRecoveryJvmTest
to use androidx.room.Room instead of the incorrect androidx.room3.Room
namespace.
---
Outside diff comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt`:
- Around line 64-101: Bound the logInsertJob wait inside the
radioInterfaceService.launchSessionWork session-lease block: replace the
unbounded join in TracerouteHandlerImpl’s traceroute handling with a finite
timeout, while preserving the existing snapshot update flow after the wait.
Ensure a stalled mesh-log insert cannot prevent the lease block from returning
and session teardown or transport replacement from proceeding.
---
Nitpick comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt`:
- Around line 61-88: Extract the duplicated telemetry-to-Node transformation
from handleTelemetry and NodeManagerImpl.handleReceivedTelemetry into one shared
private helper. The helper must copy all telemetry metric fields and update
lastHeard using the existing telemetry-time fallback and
clampTimestampToNow/maxOf logic, then replace both inline implementations with
calls to it.
In
`@core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt`:
- Around line 58-230: Add rejected-session coverage in
TelemetryPacketHandlerImplTest.kt (lines 58-230) by stubbing
radioInterfaceService.runWithSessionLease for radioSession to return false,
invoking handleTelemetry, and asserting nodeManager.updateNodeForSession is
never called. Also update GeofenceMonitorTest.kt (lines 107-133) with a non-null
RadioSessionContext whose session is reported inactive, then call
onPositionReceived and verify evaluate and notifications do not run.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2c27cc90-8fc1-4c80-b353-e9265da8dff2
📒 Files selected for processing (76)
androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.ktcore/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/SessionWork.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/SessionWorkTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.ktcore/data/src/jvmTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.ktcore/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/AirQualityChartReproTest.ktcore/database/build.gradle.ktscore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseProvider.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/SwitchingDiscoveryDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationRecoveryTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerBackfillTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerRetirementTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerShutdownTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerTestFixture.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.ktcore/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AdminPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/RadioInterfaceService.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.ktcore/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKMeshIntegrationTest.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceServiceSessionTest.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/RepositoryFakesTest.ktdesktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
🚧 Files skipped from review as they are similar to previous changes (49)
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/SwitchingDiscoveryDao.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AdminPacketHandler.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseProvider.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImpl.kt
- androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt
- core/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.kt
- core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKMeshIntegrationTest.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
- core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.kt
- desktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerTestFixture.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerBackfillTest.kt
- core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/AirQualityChartReproTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationRecoveryTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerRetirementTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerShutdownTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImplTest.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
- core/testing/src/commonTest/kotlin/org/meshtastic/core/testing/RepositoryFakesTest.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt
|
@coderabbitai I verified the three outside-diff/nit findings against the current session architecture. Traceroute log-insert joinI’m keeping the current ordering rather than adding a local timeout.
The join exists to preserve parent-before-child persistence ordering. If database persistence itself needs a stronger global time bound, that should be enforced at the database operation/admission layer rather than by weakening this specific dependency. Rejected-session test coverageThe proposed telemetry assertion would test the wrong boundary. That rejection behavior is already covered by:
A direct Geofence rejection test would be additional branch-level coverage, but the implementation already places non-null-session evaluation inside Telemetry transform deduplicationI’m leaving the two small transforms local in this PR. The active radio path is Extracting a shared cross-class helper for that legacy path would add coupling and review churn without changing current behavior. This can be revisited separately if that API is retained or gains a production caller. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the files named in the follow-up, then map their declarations before
# reading only the relevant implementation/test regions.
fd -t f '^(TracerouteHandlerImpl|MeshMessageProcessorImpl|SessionWork|NodeManagerImpl|GeofenceMonitor|TelemetryPacketHandlerImpl|SessionWorkTest|NodeManagerImplTest)\.kt$' .
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt --items all
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt --items all
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/SessionWork.kt --items all
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt --items all
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt --items all
ast-grep outline core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt --items all
# Verify the stated call/lease relationships and find production uses of the
# legacy telemetry API, excluding tests.
rg -n -C 5 -e '\blogInsertJob\b' \
-e '\blaunchSessionWork\b' \
-e '\brunWhileSessionActive\b' \
-e '\bupdateNodeForSession\b' \
-e '\bhandleReceivedTelemetry\s*\(' \
-e 'traceroute_node_position' \
core/data/src/commonMain core/repository/src/commonMain core/database/src/commonMain \
-g '*.kt'
rg -nP --glob '*.kt' --glob '!**/test/**' --glob '!**/commonTest/**' --glob '!**/jvmTest/**' \
'\bhandleReceivedTelemetry\s*\(' .Length of output: 75122
I’m withdrawing these outside-diff/nit findings. 🐇💭 ✏️ Learnings added
|
|
I've now been through this PR three times as it evolved — a full pass on the first rework, an adversarial pass over the new recovery machinery, and a delta pass on the current head — so this consolidates everything against What checks out. The original four findings from my first review are genuinely resolved, and most of what my later passes flagged has been fixed along the way: the Two small items I'd like fixed before merge: 1. A throwing transport factory still crashes the process from two call sites. The rollback inside 2. LRU eviction can delete a pending route's destination — the recovery's commit proof. The eviction exclusions cover logically-retired/detached/legacy/default, and Follow-ups, fine as separate issues: 3. Nothing bounds the merge, and the new lease chaining widens the blast radius. 4. The merge marker is still name-presence-only. 5. Minor: the same-address branch in With 1 and 2 in, this has my approval — the iteration quality on this PR has been excellent. |
8567039 to
4d9807a
Compare
|
@jamesarich Thanks again for the focused third pass. Both approval blockers are addressed in the latest head. Transport-factory failure containmentThe
The added tests verify that a throwing factory publishes no replacement transport, leaves no active failed session, and allows a later retry with a strictly newer generation. Pending-route eviction protectionCache enforcement now resolves all persisted pending source and destination database names while holding the manager mutex and excludes both endpoints from LRU candidates. Protected databases remain counted toward the cache limit, so cleanup can intentionally remain over limit while recovery evidence exists. This preserves both the source required for pre-commit fallback and the destination that may contain the committed merged copy and its marker. The tests cover multiple pending routes and verify that even the oldest route endpoints cannot be selected for eviction while an unrelated safe candidate can. I also clarified the disconnected same-address repair behavior and corrected the I agree that bounding the complete merge/writer-gate/session-lease chain and adding route-bound or versioned markers should be focused follow-ups rather than local fixes inside this final amendment. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt (1)
66-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
runForSession/runWhileForSessionwrappers across handler classes.These two-line delegating wrappers to
radioInterfaceService.runIfSessionActive/runWhileSessionActiveare duplicated verbatim inMeshConfigFlowManagerImpl(seerunForSession(session)at line 148 andrunWhileForSession(session)at line 224 in that file). Consider hoisting these as extension functions directly onRadioInterfaceService(e.g.RadioInterfaceService.runForSession(...)) so every handler (AdminPacketHandlerImpl,MeshDataHandlerImpl,TelemetryPacketHandlerImpl, etc.) shares one implementation instead of re-declaring it per class.♻️ Suggested extraction
// e.g. in core/repository RadioInterfaceService.kt fun RadioInterfaceService.runForSession(session: RadioSessionContext, block: () -> Unit): Boolean = runIfSessionActive(session, block) suspend fun RadioInterfaceService.runWhileForSession(session: RadioSessionContext, block: suspend () -> Unit): Boolean = runWhileSessionActive(session, block)Also applies to: 166-171
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt` around lines 66 - 71, Extract the duplicated runForSession and runWhileForSession delegation into shared extension functions on RadioInterfaceService, reusing runIfSessionActive and runWhileSessionActive. Remove the corresponding private wrappers from MeshConfigHandlerImpl and MeshConfigFlowManagerImpl, and update affected handlers to use the shared extensions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.kt`:
- Around line 66-71: Extract the duplicated runForSession and runWhileForSession
delegation into shared extension functions on RadioInterfaceService, reusing
runIfSessionActive and runWhileSessionActive. Remove the corresponding private
wrappers from MeshConfigHandlerImpl and MeshConfigFlowManagerImpl, and update
affected handlers to use the shared extensions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b0c1c6cf-ea5a-46a7-9f57-2a7de22bda0b
📒 Files selected for processing (77)
androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.ktcore/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/SessionWork.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.ktcore/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/SessionWorkTest.ktcore/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.ktcore/data/src/jvmTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.ktcore/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/AirQualityChartReproTest.ktcore/database/build.gradle.ktscore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseConstants.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseProvider.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/SwitchingDiscoveryDao.ktcore/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationRecoveryTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerBackfillTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerEvictionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerRetirementTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerShutdownTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerTestFixture.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.ktcore/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.ktcore/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AdminPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/RadioInterfaceService.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.ktcore/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.ktcore/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.ktcore/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.ktcore/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKMeshIntegrationTest.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.ktcore/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceServiceSessionTest.ktcore/testing/src/commonTest/kotlin/org/meshtastic/core/testing/RepositoryFakesTest.ktdesktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
🚧 Files skipped from review as they are similar to previous changes (72)
- core/database/build.gradle.kts
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/MeshServiceOrchestrator.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/FromRadioPacketHandler.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/ReceivedRadioFrame.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/SessionWork.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/StoreForwardPacketHandler.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PacketHandler.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AdminPacketHandler.kt
- core/takserver/src/commonTest/kotlin/org/meshtastic/core/takserver/TAKMeshIntegrationTest.kt
- core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeDatabaseManager.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TelemetryPacketHandler.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDaoTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/TracerouteSnapshotRepositoryImpl.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseProvider.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshDataHandler.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/TracerouteHandler.kt
- core/common/src/commonMain/kotlin/org/meshtastic/core/common/database/DatabaseManager.kt
- desktopApp/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigHandler.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/PacketHandlerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/MeshLogDaoTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/SessionWorkTest.kt
- core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/AirQualityChartReproTest.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshConfigFlowManager.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
- androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MeshMessageProcessor.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseMerger.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/GeofenceMonitorTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/AdminPacketHandlerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerWriterAdmissionTest.kt
- core/testing/src/commonTest/kotlin/org/meshtastic/core/testing/RepositoryFakesTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/GeofenceMonitor.kt
- core/testing/src/commonTest/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceServiceSessionTest.kt
- core/data/src/jvmTest/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImplTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/MeshLogRepositoryImpl.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/RadioInterfaceService.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/dao/PacketDaoAtomicTransactionTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/NodeManagerImpl.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerConnectionIdentityTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/PacketRepositoryImpl.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseMergerTest.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/RadioControllerImpl.kt
- core/database/src/jvmTest/kotlin/org/meshtastic/core/database/DatabaseManagerPendingRouteRecoveryJvmTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerBackfillTest.kt
- core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeRadioInterfaceService.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/SwitchingDiscoveryDao.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TracerouteHandlerImpl.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigHandlerImplTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImplTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerRetirementTest.kt
- core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeManager.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/SharedRadioInterfaceServiceLivenessTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImplTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerTestFixture.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/MeshServiceOrchestratorTest.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerShutdownTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/FromRadioPacketHandlerImpl.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/TelemetryPacketHandlerImpl.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/StoreForwardPacketHandlerImpl.kt
- core/database/src/commonTest/kotlin/org/meshtastic/core/database/DatabaseManagerAssociationRecoveryTest.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImpl.kt
- core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerImpl.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshConfigFlowManagerImplTest.kt
- core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/NodeManagerImplTest.kt
- core/service/src/commonTest/kotlin/org/meshtastic/core/service/RadioControllerImplTest.kt
- core/service/src/commonMain/kotlin/org/meshtastic/core/service/SharedRadioInterfaceService.kt
- core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt
Pull request was converted to draft
|
dfec490 to
478f2e5
Compare
Move packet-related read-modify-write operations into Room DAO transactions so each logical state change commits as one unit. This covers contact last-read advancement, packet and reaction status updates, SFPP hash-prefix matching, and chunked packet deletion with its dependent reactions. Keeping these operations inside the DAO prevents concurrent callers from observing an intermediate read and writing back stale state. It also ensures a late failure cannot leave only half of a packet/reaction update committed. The repository continues to enter these operations through DatabaseProvider.withDb, preserving the cross-device writer-admission contract established for database association. Add deterministic regression coverage for the behavioral invariants, including monotonic concurrent last-read updates, strict SFPP prefix matching, trigger- injected mid-operation failures, packet/reaction rollback, and rollback across the SQLite bind-limit chunk boundary.
…actional Consolidate mesh-log UUID cleanup and traceroute snapshot replacement into Room transactions. Mesh-log selection still parses the Kotlin protobuf snapshot to identify local-stats telemetry, while the selected UUID deletions now commit atomically across SQLite-safe chunks. Traceroute replacement validates snapshot ownership before mutation, deletes the previous rows, and inserts the new rows inside one transaction. Keep mesh-log selection and deletion under one DatabaseProvider.withDb admission and one captured Room instance. Move only protobuf conversion and parsing onto the injected compute dispatcher so large telemetry snapshots do not occupy the temporary single-lane database dispatcher. A device switch therefore cannot select UUIDs from one database and delete matching UUIDs from another, and an empty selection avoids opening a write transaction entirely. Preserve the existing authoritative-snapshot behavior: an empty traceroute map removes the stored snapshot, and local-stats selection remains outside SQL where the protobuf payload can be decoded safely. Coroutine cancellation propagates through the dispatcher handoff and the surrounding database admission. Add cross-chunk success and trigger-injected rollback coverage. A failure in a later mesh-log chunk restores earlier deletions, and a traceroute insert failure after deletion restores the original node positions.
Harden cross-transport database association as one protocol spanning transport admission, configuration persistence, Room merge ownership, cache retirement, device switching, and orderly shutdown. Bind every received payload to the real transport address and monotonically increasing session generation at callback admission, copying bytes into an immutable ByteString envelope before they enter the FIFO. Carry that provenance through decoding and configuration dispatch instead of reconstructing it from mutable address or generation flows when a delayed packet is handled. Publish generation, address, node number, and optional factory hardware ID as one coherent ConnectionIdentity. Retain node-number fallback for radios without a usable device ID. Reconcile identities with an atomic StateFlow update so a stale identity is removed without erasing one already published for a replacement generation. Preserve structured cancellation, log recoverable association failures, and allow fatal Errors to propagate. Model transport authority with explicit lifecycle leases. Session teardown closes callback and new-work admission immediately, while a private lifecycle token stays published until every operation admitted for that generation returns. Count those leases under the callback lock so independent packet side effects can acquire a nested lease before their parent packet operation releases its own. Teardown waits for the count to drain before clearing the public lifecycle token, closing the old transport, or publishing a replacement generation. An admitted lease therefore remains authoritative through a destination transaction commit even after late work has been rejected. Retain a serialized session-operation lane alongside counted leases. Handshake reset and configuration persistence use that lane to preserve FIFO ordering: the MyNodeInfo reset queues undispatched before the frame consumer returns, so later device, module, channel, UI, region-preset, file-manifest, metadata, or node-list persistence cannot overtake it. Independently deferred packet persistence, notifications, audit insertion, and service emission use their own counted leases instead, avoiding self-deadlock while ensuring teardown still drains them. Make transport teardown cancellation-safe once revocation begins. Complete lease drain, transport state reset, transport close, service-scope replacement, and the final disconnect publication under NonCancellable ownership. Use finally blocks so a cancellation or fatal failure from the polite-disconnect path cannot strand a revoked transport in the service. Keep replacement admission serialized by the transport mutex and reject stale close-time callbacks through the closed session gate. Propagate the owning session through the complete configuration handshake: MyNodeInfo, metadata, device and module config, channels, UI config, regional presets, file manifests, node snapshots, stage completion, and reboot-driven configuration requests. Synchronous state mutation shares callback admission; asynchronous repository work enters the ordered session lane. Await status and Stage-2 NodeInfo persistence before publication, recheck revocation between operations, and publish NodeDB readiness and Connected state only for the current session. Stale completions cannot advance progress, lockdown state, or post-handshake side effects. Authorize DatabaseManager association from the immutable handshake session and require every caller to provide explicit authority. Recheck authority before claim writes, database construction, pending-route publication, merge finalization, and routing repair. Hold the transport lifecycle lease through DatabaseMerger's Room transaction return, making session rollover and transaction commit mutually exclusive. Keep first and final transaction checks as defensive rollback guards for direct or future callers. Coordinate association with the writer-admission gate, bounded source-writer drain, marker-backed recovery, synchronous canonical publication, and logical retirement. A pre-commit rollover rolls back every copied table and releases blocked writers back to the source. A committed merge releases them to the destination, persists address routing and retirement intent, and remains canonical even if later metadata finalization requires recovery. Persisted retirement is reclaimed at the next safe manager startup; published and detached Room pools stay alive until orderly shutdown proves consumers have stopped. Retain session provenance for the entire inbound packet pipeline, not only admin configuration. Bind text and reaction persistence, routing ACK updates, SFPP, telemetry, traceroute snapshots, Store & Forward history, node updates, geofence evaluation, discovery callbacks, audit records, packet emission, client state, and notifications to the generation that admitted the packet. Require explicit, non-null authority at the telemetry and traceroute interfaces so neither can silently enter a trusted sessionless persistence path. Deferred jobs enter their nested lease undispatched. Early packets retain their session in the buffer, flush serially, discard superseded generations, and return the current packet to the queue if NodeDB readiness regresses. Keep node transforms side-effect-free because CAS contention may evaluate them more than once. Publish effects only after a successful state transition. Serialize persistence through fixed per-node stripes, read the latest in-memory snapshot inside the lane, honor the write-disable gate, and route radio-originated scheduled writes through the originating session lease. This prevents an earlier suspended upsert from overwriting a newer node state or resuming against a replacement device database. Fail device switches closed when the previous database cannot be restored. Never restore a preference or transport for the old device while the new database may remain active. Disable node writes, clear identity, node state, buffered packets, and notifications, then independently attempt persisted and transport deselection so one best-effort cleanup failure cannot skip the safety-critical transport reset. When database restoration succeeds, restore ancillary state without masking the original switch failure. Treat a matching saved preference as an already-active no-op only when transport acceptance or the synchronous transport snapshot proves the requested address is selected; otherwise throw before persisting or reporting success. Make transport construction failure-safe. If the factory throws after session publication, revoke admission, drain any synchronously admitted work, restore the prior connection state, clear partial transport fields, and rethrow the original failure. Keep the consumed generation monotonic so retry callbacks cannot impersonate the failed session. Align the fake transport with production: address selection alone creates no session, active restart advances the generation, and disconnect drains admitted work before lifecycle completion. Protect deferred cache enforcement against intervening switches by resolving the active database only under the manager mutex. Exclude active-writer and detached pools from eviction, retry enforcement after writers drain, and remove cached ownership only after a successful close. Never delete files after a close failure, and retain retirement intent whenever cleanup must be retried. Let cancellation stop best-effort destructive eviction rather than forcing it through NonCancellable execution. Never replay an arbitrary withDb callback after it starts. A callback may have completed one operation before a closed-pool or acquisition-timeout failure, so a retry could duplicate a side effect or split one logical write across databases. Recover the wedged active pool only for later admissions, preserve the original callback failure, and attach reopen failure as suppressed context. Keep default-pool creation, current-flow publication, and shutdown acquisition under construction-safe synchronization so lazy initialization cannot race cache mutation or teardown. Track manager operations, admitted writers, detached pools, and lazy manager jobs explicitly. Treat CLOSING as retryable ownership: reject new work, cancel manager jobs before waiting for their operation tokens, bound drains, close each distinct Room pool once, and retain state for a later close attempt when cleanup cannot finish. Application teardown cancels consumers before invoking the manager's suspending close bridge. Keep runtime diagnostics privacy-safe: raw transport addresses and factory identifiers are used only for authority and routing checks, while session, identity, handshake, and association logs redact them. Leave the established persistent audit-record payload contract unchanged. Add deterministic barrier-based coverage for stale and same-address generations, queued and mid-admission MyNodeInfo, ordered complete config clearing, serialized handshake work alongside concurrent nested leases, revocation during Stage-2 installation, stale publication, transaction-time association rollover, routing repair, writer admission, failed switch rollback, rejected same-address selection, factory retry, fake lifecycle boundaries, deferred packet rejection, node persistence ordering, cancellation-safe teardown, bounded and retryable shutdown, concurrent close, detached pools, lazy initialization, startup retirement, real merge-marker recovery, early-buffer readiness regression, explicit telemetry authority, and session-bound audit and notification work.
478f2e5 to
9c85d58
Compare
Overview
This hardens database updates and cross-transport device association during connection changes.
Several packet, reaction, mesh-log, and traceroute operations previously performed related reads and writes through separate DAO calls. Failure, cancellation, or concurrent updates between those calls could leave partial or lost state.
Database association also needed stronger coordination between active operations, transport-session authority, merge publication, routing recovery, database retirement, and shutdown. Delayed callbacks or queued frames from an older BLE, TCP, or USB session could otherwise continue after a replacement connection became active and affect the wrong device database.
This builds on #6096’s cross-transport database unification, #6228’s trusted device-identity migration, #6231’s retry-idempotent merge marker, #6233’s write-through-
withDbmerge barrier, and #6236’s switching Discovery DAO.Foundation for: #6255, #6259
Review guide
The three commits are intended to be reviewed in order:
The first two commits establish the transactional primitives. The third commit applies them to transport changes and contains most of the lifecycle and concurrency tests.
Scope boundary
This PR removes raw transport identifiers and payloads from diagnostic string output, but it does not redesign the persisted
MeshLogpayload or retention contract. That storage is consumed by telemetry, position, traceroute, and diagnostic-history features and requires a separate compatibility and privacy audit before its schema or retained payloads can safely change.Changes
Validation
Added deterministic coverage for:
The corresponding source head passed the Android build, lint and KMP smoke compile, all applicable test shards, screenshot validation, and the aggregate workflow check.
Extended debug testing exercised telemetry, positions, messages, configuration, database cleanup, Activity restarts, weak-signal BLE disconnects, reconnection, and device switching without a closed Room pool, SQLite lock, database-acquisition timeout, stale database association, or process crash.
Summary by CodeRabbit