Skip to content

Coalesce conversation reloads instead of restarting the loader - #2243

Merged
mpretty-cyro merged 2 commits into
devfrom
fix/conversation-loader-coalesce
Nov 5, 2026
Merged

mpretty-cyro merged 2 commits into
devfrom
fix/conversation-loader-coalesce

Conversation

@mpretty-cyro

@mpretty-cyro mpretty-cyro commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Contributor checklist

  • I have tested my contribution on these devices:
  • Virtual device: Pixel 6 profile, API 37 (arm64), play flavour, debug
  • My contribution is fully baked and ready to be merged as is
  • I ensure that all the open issues my contribution fixes are mentioned in the commit message of my first commit using the Fixes #1234 syntax

Description

In the same ANR cluster, the main thread waits for a connection while binding conversation rows
(ConversationAdapter.getItemViewType/onBindItemViewHolder → MmsDatabase.Reader.getQuote). The connections are held by
several ConversationLoader loads at once (up to four ModernAsyncTask threads in getCount()/fillWindow).

restartConversationLoader() called LoaderManager.restartLoader, which abandons the running load without stopping it, so each
reload trigger (debounced message, reaction and attachment changes) added another concurrent query. It now signals
onContentChanged() on the existing loader when the thread ID hasn't changed, so AsyncTaskLoader runs one load at a time and
coalesces the rest; it still restarts when the thread ID changes.

Each activity instance still restarts the loader once before it starts coalescing. The loader survives a configuration change, but only restartLoader/initLoader bind it to the new instance's callbacks; without that, rotating or switching theme left the conversation empty.

This reduces contention; it doesn't remove the main-thread row reads in the cursor adapter.

Testing: on an emulator with a debug-only delay in ConversationLoader (not in this PR) and a concurrency counter, eight rapid sends ran up to 3 loads at once on dev, and at most 1 with this change; the list still updated. Rotating an open conversation and rotating back keeps its messages, as on dev. The adapter-bind ANR itself wasn't reproduced.

Every reload trigger called restartLoader, which abandons the running
load without stopping it. Its query holds a database connection until
the cursor window is filled, so during bursts of message, reaction or
attachment changes several loads ran at once (up to four in ANR
samples) and the main thread waited for a connection while binding
rows. Signal a content change on the existing loader instead, which runs
one load at a time; restart only when the thread ID has changed.

@mpretty-cyro mpretty-cyro left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

.

A recreated activity found the retained loader and only signalled a
content change, so its own callbacks never received a load and the
conversation stayed empty. Each instance now restarts the loader once.

@mpretty-cyro mpretty-cyro left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

lgtm

@mpretty-cyro
mpretty-cyro marked this pull request as ready for review November 5, 2026 03:10
@mpretty-cyro
mpretty-cyro merged commit 2ac0028 into dev Nov 5, 2026
8 of 10 checks passed
@mpretty-cyro
mpretty-cyro deleted the fix/conversation-loader-coalesce branch November 5, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant