Description
Expected Behavior
App doesn't crash
Actual Behavior
App crashes due to SQLiteCipher on Android API 24-25
Steps to Reproduce
App uses Room + SQLCipher.
WAL is enabled.
The only synthetic case how I can emulate crash is accessing SQLiteCompiledSql
(Use API 24-25 Android emulator):
- Launch thread A
- Launch thread B
- Block B, In A create
SQLiteCompiledSql
object, setnStatement
so thatfinalize
can runreleaseSqlStatement
, block A, unblock B - In B launch start a long transaction, unblock A, do memory extensive work to cause GC
- Wait
Android test Kotlin code(if required I can convert to Java):
@Test
fun emulateCrash() = coroutineRule.runBlocking {
dbRule.useWithProject {
val latch1 = CountDownLatch(1)
val latch2 = CountDownLatch(1)
launch (Dispatchers.IO) {
SQLiteProxy(db.openHelper.writableDatabase as SQLiteDatabase, "SELECT 1 FROM Album").setupStub()
latch2.countDown()
latch1.await()
}
latch2.await()
db.withTransaction {
latch1.countDown()
while (true) {
// no matter what - just pollute memory to cause GC
val albumEntity = newAlbum(
projectEntity = projectEntity,
companyEntity = companyEntity
)
}
}
}
}
package net.sqlcipher.database;
public class SQLiteProxy extends SQLiteCompiledSql {
public SQLiteProxy(SQLiteDatabase db, String sql) {
super(db, sql);
}
public void setupStub() {
nStatement = Long.MAX_VALUE;
}
}
SQLCipher version (can be identified by executing PRAGMA cipher_version;
):
4.4.2 community
SQLCipher for Android version:
4.4.2
Room version:
2.3.0-beta01
My guess would be how Android handles GC and especially finalize()
on version API 24-25.
Since there is DB lock() call that blocks/await thread this causes watchdog to throw exception.
I've checked generated Room code and I don't see where the cursor can be not closed.
Not sure when the compiled statement is not cached to emulate the scenario.
However, it happens in normal code execution with extensive concurrent work and multiple transactions.
Can releaseSqlStatement
be removed from finalize
or handled in a different way?