Rajeev Shetty
Rajeev Shetty

Reputation: 1764

Android: How to simulate CursorWindowAllocationException crash

I want to simulate android.database.CursorWindowAllocationException crash. I am working on a huge codebase with a lot of room database queries in each screen. I am not sure in which database query this crash occurs and in which scenario this crash occurs. So is there any way to simulate this crash? I tried keeping almost 10 database cursors open in a for loop but could not reproduce the crash. I have attached the crashlytics logs for reference.

Fatal Exception: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. 
       at android.database.CursorWindow.<init>(CursorWindow.java:108)
       at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
       at android.database.sqlite.SQLiteCursor.clearOrCreateWindow(SQLiteCursor.java:309)
       at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
       at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
       at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:220)
       at android.database.AbstractCursor.moveToNext(AbstractCursor.java:269)
       at androidx.room.InvalidationTracker$1.checkUpdatedTable(InvalidationTracker.java:461)
       at androidx.room.InvalidationTracker$1.run(InvalidationTracker.java:431)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
       at java.lang.Thread.run(Thread.java:764)

Upvotes: 0

Views: 930

Answers (1)

MikeT
MikeT

Reputation: 56928

The following simulates such a crash (but traps it):-

    var lastIndex = 0
    try
    {
        for (i in 0..1999) {
            lastIndex = i
            val csr: Cursor = db.openHelper.writableDatabase.query(SimpleSQLiteQuery("SELECT * FROM model"))
            val count = csr.count.toLong()
        }
    }catch (  e:java.lang.Exception)
    {
        Log.d(
            "DBEXCEPTION",
            "Exception trapped at INDEX " + lastIndex + " Message was " + e.message
        )
        e.printStackTrace()
    }
    }
  • where db is an instance of an @Database annotated class (etc).
  • 0..1999 as 2000 open Cursors should be enough to always fail
    • note that empty Cursors do not appear to result in a failure and not actually traversing the Cursor in some way doesn't result in the a failure. hence the csr.count.toLong
    • I suspect that the file underlying the Cursor is created/opened and closed properly by SQLite and it is not until an attempt is made to access the Cursor that the file is actually opened and then left open until the file is closed.

The log will then contain something like:-

2022-09-20 09:48:01.929 E/CursorWindow: CursorWindow: mmap() failed: errno=12.
2022-09-20 09:48:01.930 D/DBEXCEPTION: Exception trapped at INDEX 1415 Message was Could not allocate CursorWindow '/data/user/0/a.a.so73757679kotlinroomuniqueconflict/databases/the_database.db' of size 2097152 due to error -12.
2022-09-20 09:48:01.930 W/System.err: android.database.CursorWindowAllocationException: Could not allocate CursorWindow '/data/user/0/a.a.so73757679kotlinroomuniqueconflict/databases/the_database.db' of size 2097152 due to error -12.
2022-09-20 09:48:01.930 W/System.err:     at android.database.CursorWindow.nativeCreate(Native Method)
2022-09-20 09:48:01.930 W/System.err:     at android.database.CursorWindow.<init>(CursorWindow.java:139)
2022-09-20 09:48:01.930 W/System.err:     at android.database.CursorWindow.<init>(CursorWindow.java:120)
2022-09-20 09:48:01.930 W/System.err:     at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:202)
2022-09-20 09:48:01.931 W/System.err:     at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
2022-09-20 09:48:01.931 W/System.err:     at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:140)
2022-09-20 09:48:01.931 W/System.err:     at android.app.Activity.performCreate(Activity.java:7994)
2022-09-20 09:48:01.931 W/System.err:     at android.app.Activity.performCreate(Activity.java:7978)
2022-09-20 09:48:01.931 W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
2022-09-20 09:48:01.931 W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
2022-09-20 09:48:01.931 W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
2022-09-20 09:48:01.931 W/System.err:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
2022-09-20 09:48:01.931 W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
2022-09-20 09:48:01.931 W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
2022-09-20 09:48:01.931 W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
2022-09-20 09:48:01.932 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
2022-09-20 09:48:01.932 W/System.err:     at android.os.Looper.loop(Looper.java:223)
2022-09-20 09:48:01.932 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:7656)
2022-09-20 09:48:01.932 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2022-09-20 09:48:01.932 W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
2022-09-20 09:48:01.932 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2022-09-20 09:48:02.003 W/libc: malloc(4194304) failed: returning null pointer
2022-09-20 09:48:02.003 A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x80000 in tid 23176 (RenderThread), pid 23153 (muniqueconflict)

It should be noted that the issue at hand is not exactly the number of Cursors but that underlying a Cursor is a file and that (I believe) the issue is the number of unclosed file allocations of which the database itself will have 3 (for the database, the -wal file and the -shm file).

However, there may also be a memory related issue, as this test then results in the malloc error (failed to get 4Mb).

Upvotes: 1

Related Questions