From 613dee91bf88cd5a9cfdbddc176db094042c6f3e Mon Sep 17 00:00:00 2001 From: James Rich Date: Sat, 13 Jun 2026 07:27:56 -0500 Subject: [PATCH] fix(database): use a single connection for in-memory test databases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit configureCommon() applied setMultipleConnectionPool(maxNumOfReaders = 4) to every database, including the in-memory ones used by tests. A read on a pooled reader connection can observe a snapshot older than the latest write on the writer connection, so a read immediately after a write may return stale rows. DeviceLinkRepositoryImplTest.reconcilePrunesShortCodesNoLongerInCatalog read [a, b] (the pre-prune state) instead of [a] after a deleteNotIn — passing locally but flaking on CI depending on connection-assignment timing (failed shard-core on #5738; the identical code passed on #5780). In-memory builders now pass multiConnection = false so reads serialize behind writes on one connection. Production/file databases keep the multi-reader pool. Co-Authored-By: Claude Fable 5 --- .../core/database/DatabaseBuilder.kt | 2 +- .../core/database/MeshtasticDatabase.kt | 19 ++++++++++++++----- .../core/database/DatabaseBuilder.kt | 2 +- .../core/database/DatabaseBuilder.kt | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt b/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt index 8e9fbbac2..e5e97fcfb 100644 --- a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt +++ b/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt @@ -45,7 +45,7 @@ actual fun getDatabaseBuilder(dbName: String): RoomDatabase.Builder = Room.inMemoryDatabaseBuilder(factory = { MeshtasticDatabaseConstructor.initialize() }) - .configureCommon() + .configureCommon(multiConnection = false) .setDriver(BundledSQLiteDriver()) /** Returns the Android directory where database files are stored. */ diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt index bea88198d..4baa33afb 100644 --- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt +++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt @@ -139,11 +139,20 @@ abstract class MeshtasticDatabase : RoomDatabase() { abstract fun discoveryDao(): DiscoveryDao companion object { - /** Configures a [RoomDatabase.Builder] with standard settings for this project. */ - fun RoomDatabase.Builder.configureCommon(): RoomDatabase.Builder = - this.fallbackToDestructiveMigration(dropAllTables = false) - .setMultipleConnectionPool(maxNumOfReaders = 4, maxNumOfWriters = 1) - .setQueryCoroutineContext(ioDispatcher) + /** + * Configures a [RoomDatabase.Builder] with standard settings for this project. + * + * @param multiConnection opens a multi-reader connection pool for concurrent reads. Production/file databases + * want this. In-memory databases (tests) MUST pass `false`: a pooled reader connection can serve a snapshot + * older than the latest write on the writer connection, so a read immediately after a write may observe stale + * rows — making read-after-write assertions non-deterministically flaky (see `DeviceLinkRepositoryImplTest`). + * A single connection serializes reads behind writes. + */ + fun RoomDatabase.Builder.configureCommon( + multiConnection: Boolean = true, + ): RoomDatabase.Builder = this.fallbackToDestructiveMigration(dropAllTables = false) + .apply { if (multiConnection) setMultipleConnectionPool(maxNumOfReaders = 4, maxNumOfWriters = 1) } + .setQueryCoroutineContext(ioDispatcher) } } diff --git a/core/database/src/iosMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt b/core/database/src/iosMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt index 459c024ea..6cecf57f1 100644 --- a/core/database/src/iosMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt +++ b/core/database/src/iosMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt @@ -51,7 +51,7 @@ actual fun getDatabaseBuilder(dbName: String): RoomDatabase.Builder = Room.inMemoryDatabaseBuilder(factory = { MeshtasticDatabaseConstructor.initialize() }) - .configureCommon() + .configureCommon(multiConnection = false) .setDriver(BundledSQLiteDriver()) /** Returns the iOS directory where database files are stored. */ diff --git a/core/database/src/jvmMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt b/core/database/src/jvmMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt index 9686529d7..50e84898d 100644 --- a/core/database/src/jvmMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt +++ b/core/database/src/jvmMain/kotlin/org/meshtastic/core/database/DatabaseBuilder.kt @@ -57,7 +57,7 @@ actual fun getDatabaseBuilder(dbName: String): RoomDatabase.Builder = Room.inMemoryDatabaseBuilder(factory = { MeshtasticDatabaseConstructor.initialize() }) - .configureCommon() + .configureCommon(multiConnection = false) .setDriver(BundledSQLiteDriver()) /** Returns the JVM/Desktop directory where database files are stored. */