Skip to content

fix(deck-picker): do not block the main thread on startup during sync#21334

Open
criticalAY wants to merge 1 commit into
ankidroid:mainfrom
criticalAY:fix/sync-anr
Open

fix(deck-picker): do not block the main thread on startup during sync#21334
criticalAY wants to merge 1 commit into
ankidroid:mainfrom
criticalAY:fix/sync-anr

Conversation

@criticalAY

Copy link
Copy Markdown
Contributor

Note

Assisted-by: Claude Opus 4.8 (some diagnosis and a few parts of code)

Purpose / Description

Fixes an ANR when the DeckPicker is recreated while a sync is stuck on an unresponsive server (for example AnkiWeb being down).

Fixes

Approach

See commit

How Has This Been Tested?

Unit test

Learning (optional, can help others)

NA

Checklist

Please, go through these checks before submitting the PR.

  • 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

DeckPicker recreation (for example a light/dark theme change) ran
InitialActivity.getStartupFailureType on the main thread, which waits on
the collection queue. A sync stuck on an unresponsive server holds that
queue for the entire network call, so the recreated activity froze and
ANRed after 5 seconds. Run the check on Dispatchers.IO and deliver the
result through flowOfStartupResponse instead.

Assisted-by: Claude Opus 4.8 (some part of the PR)
@david-allison

david-allison commented Jul 7, 2026

Copy link
Copy Markdown
Member

Could you provide reproduction instructions (probably a patch which adds a delay) so we can reproduce it on a physical device.

The startup path has historically been nasty. This initially looks good (and the ViewModel makes things easier), but I'd like to run it through a debugger to confirm.

@criticalAY

Copy link
Copy Markdown
Contributor Author
Subject: [PATCH] repro: sync anr
---
Index: AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt	(revision 25da95364f62624be98404df64afae4d699f134c)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt	(date 1783413600156)
@@ -159,6 +159,8 @@
             manualCancelButton = R.string.dialog_cancel,
         ) {
             withCol {
+                Timber.w("#21305 repro: holding the collection queue for 60s")
+                Thread.sleep(60_000)
                 syncCollection(auth2, syncMedia = false) // media is synced by SyncMediaWorker
             }
         }
Index: AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt	(revision 25da95364f62624be98404df64afae4d699f134c)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt	(date 1783413571943)
@@ -522,17 +522,11 @@
         }
 
         Timber.d("handleStartup: Continuing after permission granted")
-        viewModelScope.launch {
-            // opening the collection waits on the collection queue, which a sync stuck on an
-            // unresponsive server can hold for a long time. Waiting on the main thread here
-            // froze the DeckPicker when it was recreated
-            val failure =
-                withContext(Dispatchers.IO) {
-                    InitialActivity.getStartupFailureType(environment::initializeAnkiDroidFolder)
-                }
+        run {
+            val failure = InitialActivity.getStartupFailureType(environment::initializeAnkiDroidFolder)
             if (failure != null) {
                 flowOfStartupResponse.value = StartupResponse.FatalError(failure)
-                return@launch
+                return
             }
 
             // successful startup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If ankiweb does not respond, the UI does not respond

2 participants