diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt index e96ce887c..42dd56a6a 100644 --- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt +++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt @@ -169,7 +169,7 @@ open class DatabaseManager( private val limitedIo = dispatchers.io.limitedParallelism(4) - /** Execute [block] with the current DB instance. */ + /** Execute [block] with the current DB instance. Retries once if the pool closes during a DB switch. */ @Suppress("TooGenericExceptionCaught") override suspend fun withDb(block: suspend (MeshtasticDatabase) -> T): T? = withContext(limitedIo) { val db = _currentDb.value ?: return@withContext null @@ -180,18 +180,33 @@ open class DatabaseManager( } catch (e: CancellationException) { throw e // Preserve structured concurrency cancellation propagation. } catch (e: Exception) { - // If the connection pool was closed between capturing `db` and executing the query - // (e.g., during a database switch), retry once with the current DB instance. - if (e.message?.contains("Connection pool is closed") == true) { - Logger.w { "withDb: connection pool closed, retrying with current DB" } - val retryDb = _currentDb.value ?: return@withContext null - block(retryDb) + // If the active database switched while we held a reference to the old one, + // and the exception indicates a closed pool/connection, retry with the new DB. + val retryDb = _currentDb.value + if (retryDb != null && retryDb !== db && isDbClosedException(e)) { + Logger.w { "withDb: database closed during switch (${e.message}), retrying with current DB" } + try { + block(retryDb) + } catch (retryEx: Exception) { + retryEx.addSuppressed(e) + throw retryEx + } } else { throw e } } } + private fun isDbClosedException(e: Exception): Boolean = generateSequence(e) { it.cause } + .any { throwable -> + val msg = throwable.message?.lowercase() ?: return@any false + "closed" in msg && DB_TERMS.any { it in msg } + } + + private companion object { + val DB_TERMS = listOf("pool", "database", "connection", "sqlite") + } + /** * Returns true if a database exists for the given device address. Android Room stores DB files without an * extension; JVM/iOS append `.db`. We check both to stay platform-agnostic.