Skip to content

CardContentProvider: Avoid eager materialization of search results - #22290

Open
david-allison wants to merge 6 commits into
ankidroid:mainfrom
david-allison:air/19572-10-2456fe14-e37
Open

david-allison wants to merge 6 commits into
ankidroid:mainfrom
david-allison:air/19572-10-2456fe14-e37

Conversation

@david-allison

Copy link
Copy Markdown
Member

Note

Assisted-by: GPT-6

Purpose / Description

As discussed:

A small AbstractCursor subclass holding just the cardIds, rendering one row on demand, so the CursorWindow bounds peak memory ,col.findCards() and projection validation stay eager so bad queries/columns still throw from query(). The other MatrixCursor sites are bounded and Schedule mutates the selected deck around its loop, so I will leave them

Fixes

Approach

  • Add a LazyCursor which handles the list of CardIds and only materializing the current row
  • A further optimization to only returns the columns we requested from the card (in the case of rendered columns such as the question/answer)
  • Add further validation
  • ⚠️ Since we are now lazily returning data, handle deletion of a row:
    • Define it as the Id remains, all other calls return null

Important

Please review the changed API semantics carefully

How Has This Been Tested?

Heavily tested

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

List the CardIds and return the current row of the cursor, rather than
pre-fetching all rows.

Reduces memory usage

Part of 20253

Assisted-by: GPT-6
Part of 20253

Assisted-by: GPT-6
Only load when a column is read, so restoring the cursor position
does not fetch an unused row.

Part of 20253

Assisted-by: GPT-6
Defers rendering questions/answers/card names until they're requested.

Part of 20253

Assisted-by: GPT-6
Part of 20253

Assisted-by: GPT-6
When loading a row, check if the card is deleted.

If it is, all columns except the ID will return `null`

Part of 20253

Assisted-by: GPT-6
Comment on lines +940 to +943
* For card searches, the matching card IDs and result count are fixed when the query runs.
* Other fields are loaded lazily and may reflect changes made while the cursor is open.
* If a card is deleted before its row is loaded, the row retains its [_ID] and returns `null`
* for all other requested columns. Rows already loaded may still contain the earlier values.

@david-allison david-allison Oct 3, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Needs to inform consumers & discuss
I suspect this won't be an issue due to consumer behavior, but it's a behavioural change

@david-allison david-allison changed the title Air/19572 10 2456fe14 e37 CardContentProvider: Avoid eager materialization of search results Oct 3, 2026

@mikehardy mikehardy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey David 👋

The search path was building a MatrixCursor of every matching Card (and rendering Q/A) before query() returned. LazyCursor holding the id list, loading one row on read, and letting CursorWindow bound peak memory is the #20253 plan. Eager findCards + column validation, _id-only fast path, and deleted-before-load rows keeping _id with nulls all look right, and the tests actually drive fillWindow / random access / the delete case.

On the self-thread: I think the CONTENT_URI KDoc is enough notice. I would not bump provider spec for this. Cross-process clients still get a window snapshot at fill time, and anyone iterating immediately never sees the live-reload edge. If a client holds the cursor across a collection mutation they can now see later values or nulls; that seems like the right trade for not OOMing large searches.

Thanks for keeping the other MatrixCursor sites out of this PR. LGTM

@mikehardy mikehardy added Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) and removed Needs Review labels Oct 4, 2026
@mikehardy
mikehardy added this pull request to the merge queue Oct 4, 2026
@david-allison
david-allison removed this pull request from the merge queue due to a manual request Oct 4, 2026
@david-allison

Copy link
Copy Markdown
Member Author

I want to wait for @mikehardy and @ankidroid/reviewers for this one

Technically a breaking change in the API contract regarding concurrent updates and I'd like a few +1s to be sure I've not overlooked anything

@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge Needs reviewer reply Waiting for a reply from another reviewer and removed Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) labels Oct 4, 2026
@mikehardy

Copy link
Copy Markdown
Member

I saw the demote — fair call if you want another set of eyes on the live-cursor edge.

From my side I am still +1 on the approach in the Approve: CONTENT_URI KDoc as the consumer notice, no provider.spec bump, and the trade for not OOMing large searches. Happy to leave this on Needs Second Approval until another reviewer lands.

@mikehardy mikehardy removed the Needs reviewer reply Waiting for a reply from another reviewer label Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Needs Second Approval Has one approval, one more approval to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CardContentProvider: Avoid eager materialization of search results

2 participants