4

I'm seeing a crash report for this occassionally:

Fatal Exception: java.lang.IllegalStateException: Couldn't read row 1127, col 0 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
   at android.database.CursorWindow.nativeGetLong(CursorWindow.java)
   at android.database.CursorWindow.getLong(CursorWindow.java:511)
   at android.database.AbstractWindowedCursor.getLong(AbstractWindowedCursor.java:75)
   at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:220)
   at android.database.AbstractCursor.moveToNext(AbstractCursor.java:245)
   at android.database.CursorWrapper.moveToNext(CursorWrapper.java:166)
   at com.anthonymandra.util.ImageUtils.cleanDatabase(SourceFile:381)

Apparently the moveToNext is failing mid-loop (note row 1127). The loop removes entries that represent files that can no longer be found.

final ArrayList<ContentProviderOperation> operations = new ArrayList<>();

try( Cursor cursor = c.getContentResolver().query(Meta.CONTENT_URI, null, null, null, null))
{
    if (cursor == null)
        return;

    final int uriColumn = cursor.getColumnIndex(Meta.URI);
    final int idColumn = cursor.getColumnIndex(BaseColumns._ID);

    while (cursor.moveToNext())
    {
        String uriString = cursor.getString(uriColumn);
        if (uriString == null)  // we've got some bogus data, just remove
        {
            operations.add(ContentProviderOperation.newDelete(
                    Uri.withAppendedPath(Meta.CONTENT_URI, cursor.getString(idColumn))).build());
            continue;
        }
        Uri uri = Uri.parse(uriString);
        UsefulDocumentFile file = UsefulDocumentFile.fromUri(c, uri);
        if (!file.exists())
        {
            operations.add(ContentProviderOperation.newDelete(Meta.CONTENT_URI)
                    .withSelection(getWhere(), new String[]{uriString}).build());
        }
    }
}

c.getContentResolver().applyBatch(Meta.AUTHORITY, operations);

Any idea how a cursor could fail mid-loop like that?

Alykoff Gali
  • 925
  • 16
  • 29
Anthony
  • 7,121
  • 3
  • 33
  • 66

2 Answers2

4

You appear to be making a fairly large query: at least 1127 rows, and for all possible columns (despite the fact that you are only using two of them). And, during your work with that Cursor, you are doing disk I/O and/or IPC back to the ContentProvider, assuming that UsefulDocumentFile is related to Android's DocumentFile.

As Prakash notes, the Cursor that you get back may contain only a subset of the information. As soon as you try advancing past that point, the Cursor needs to go back to the data source and get the next window of results. I can see you running into this sort of problem if there has been a substantial change in the data while this work has been going on (e.g., there are now fewer than 1127 rows).

I suggest that you:

  • Constrain the columns that you get back to the subset that you need, and

  • Avoid the I/O during the loop (e.g., spin through the Cursor to build up an ArrayList<Pair> or something, close the Cursor, then iterate over the list)

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • Are you suggesting that I create an array of `UsefulDocumentFile` (yes, it's a `DocumentFile` with a lot of improvements). The operations are already placed in a array and batched afterwards. The `UsefulDocumentFile` creation would operate on a different db through the SAF. Would the simple I/O or IPC cause an issue? I really should be using projections though. Ignored projections in the early creation and the habit stuck. – Anthony Nov 18 '16 at 22:35
  • @Anthony: "Are you suggesting that I create an array of UsefulDocumentFile" -- that probably works. I am not sure if `DocumentFile` does I/O in `fromUri()`. If it does, then I'd hold onto the `Uri`, waiting to create the `DocumentFile` instances until you're done with the `Cursor`. "Would the simple I/O or IPC cause an issue?" -- well, it slows things down, requiring you to keep your `Cursor` open for longer, raising the prospect of issues with windows. For small queries, where you're sure to be within a single window, it's not a big deal. – CommonsWare Nov 18 '16 at 22:39
  • I have little issues like this popping up in a few places. I grasped the `CursorWindow` idea when I saw it popping up in stack traces, but I didn't grasp the gravity of the large queries I was doing and the shifting of the window. So would you say any time you know there's a possibility of a large query try to get rid of that cursor quickly and just pull the data you need then process after closing? – Anthony Nov 18 '16 at 22:48
  • @Anthony: In general, yes. That is especially true when you are querying some third-party `ContentProvider`, just because there are more things that can go wrong (e.g., that app crashes). – CommonsWare Nov 18 '16 at 22:50
  • This sounds like the issue. Any idea if I can reissue the bounty if this expires and then award it? – Anthony Nov 18 '16 at 22:54
  • @Anthony: I'm not aware of any one-shot-then-you're-done limit of bounties on a question, though I haven't really looked into it. – CommonsWare Nov 18 '16 at 22:57
  • I couldn't reproduce this error so I gave it a few days in beta with no reports. In the end I'm fairly certain the issue was that the db deletes were taking exceptionally long leading to a race condition as I was treating cursors as immutable objects which as you pointed out is incorrect if you exceed the cursor window. – Anthony Nov 21 '16 at 20:25
3

may be you are srote file in database that's why you get java.lang.IllegalStateException

 UsefulDocumentFile file = UsefulDocumentFile.fromUri(c, uri);

Android SQLite returns rows in cursor windows that have the maximum size of 2MB as specified by config_cursorWindowSize.If your row exceeds this limit, you'll get this error.

Store files in filesystem and paths in database.

prakash ubhadiya
  • 1,155
  • 10
  • 23