androidandroid-room

Android Room crash cannot start a transaction within a transaction


I have a room database in my Android app. A recent change has resulted in crashes:

Fatal Exception: android.database.sqlite.SQLiteException: cannot start a transaction within a transaction (code 1 SQLITE_ERROR[1])
   at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
   at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:1033)
   at android.database.sqlite.SQLiteSession.beginTransactionUnchecked(SQLiteSession.java:321)
   at android.database.sqlite.SQLiteSession.beginTransaction(SQLiteSession.java:300)
   at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:970)
   at android.database.sqlite.SQLiteDatabase.beginTransactionNonExclusive(SQLiteDatabase.java:904)
   at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.beginTransactionNonExclusive(FrameworkSQLiteDatabase.kt:59)
   at androidx.room.RoomDatabase.internalBeginTransaction(RoomDatabase.kt:527)
   at androidx.room.RoomDatabase.beginTransaction(RoomDatabase.kt:511)
   at myapp.data.dao.DocumentDao_Impl$updatePosition$2.call(DocumentDao_Impl.java:1576)
   at myapp.data.dao.DocumentDao_Impl$updatePosition$2.call(DocumentDao_Impl.java:1566)
   at androidx.room.CoroutinesRoom$Companion$execute$2.invokeSuspend(CoroutinesRoom.kt:64)
   at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
   at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
   at androidx.room.TransactionExecutor.execute$lambda$1$lambda$0(TransactionExecutor.java:36)
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
   at java.lang.Thread.run(Thread.java:1012)

The dao for the call is:

@Query("update document set bookmarkLocation=:position, bookmarkLength=:length where documentId=:documentId")
abstract suspend fun updatePosition(
    documentId: String,
    position: Int,
    length: Int
)

Its not using a transaction. There's nothing above it that starts a transaction. There's nothing tricky about the entity. I don't get it, I've used Room extensively. Why is this simple update causing an issue? This is happening in production. I've don't see it in testing. In production it's causing crashes for about 1% of my users.


Solution

  • Today, I found an issue, that seems to be root cause.

    a update was causing a SQLiteConstraintException. That was being caught. In my code, there was no transaction there. Apparently there is in the room generated code.

    Once the SQLiteConstraintException happens, the nested transaction issues happens shortly after.

    The SQLiteConstraintException was due to a bug where a value was an empty string, that should have been UUID that's a key in another table.

    Once I found a way reproduce the SQLiteConstraintException, the nested transaction issue was reproducible too. Just fixing SQLiteConstraintException, makes the nested transaction issue non-reproducible. For good measure, I did put the update that had SQLiteConstraintException into transaction as well.

    Now the question is: Why did SQLiteConstraintException leave things in bad state when there was not transaction in my code.