ankidroid / Anki-Android

AnkiDroid: Anki flashcards on Android. Your secret trick to achieve superhuman information retention.
GNU General Public License v3.0
7.94k stars 2.16k forks source link

CI: Timeouts on Windows Unit Tests #16253

Closed david-allison closed 2 weeks ago

david-allison commented 2 weeks ago
old First failure seems to be ~3 days ago https://github.com/ankidroid/Anki-Android/actions/runs/8767647456/job/24061139040 But we had a similar issue with `dependency-updates` * https://github.com/ankidroid/Anki-Android/pull/16198 ---- My suspicion is that the cause is: * https://github.com/ankidroid/Anki-Android/pull/16182 But this needs triage ----

Things to do to close this:

mikehardy commented 2 weeks ago

@david-allison - are the logs still uploading? I don't see windows log artifacts and that would be a very useful thing to have to troubleshoot this

david-allison commented 2 weeks ago

I suspect the failure reason isn't 'failed', so logs are skipped

perhaps: { cancelled() || failure() } is necessary

david-allison commented 2 weeks ago

Diagnostics: https://github.com/ankidroid/Anki-Android/pull/16255#issuecomment-2073633090

--> Reviewer.addFlags --> https://github.com/ankidroid/Anki-Android/commit/a281a9a14f884e2bb8742def705873791fea95e0

david-allison commented 2 weeks ago

I see this resolution as being 4-pronged:

david-allison commented 2 weeks ago

My read (from intuition) is that withQueue is failing to uphold invariants

This may just require runTest to fix

mikehardy commented 2 weeks ago

A thrown error crashes rather than deadlocks

This one here is what I would prioritize first, fail-fast and (IMNSHO) fail-correctly vs fail-ambiguously

david-allison commented 2 weeks ago

My read (from intuition) is that withQueue is failing.

This may just require runTest to fix


To get an exception from the .bin file:

david-allison commented 2 weeks ago
net.ankiweb.rsdroid.exceptions.BackendInvalidInputException$BackendCollectionAlreadyOpenException: CollectionAlreadyOpen
at net.ankiweb.rsdroid.exceptions.BackendInvalidInputException$Companion.fromInvalidInputError(BackendInvalidInputException.kt:34)
at net.ankiweb.rsdroid.BackendException$Companion.fromError(BackendException.kt:114)
at net.ankiweb.rsdroid.BackendKt.unpackResult(Backend.kt:271)
at net.ankiweb.rsdroid.BackendKt.access$unpackResult(Backend.kt:1)
at net.ankiweb.rsdroid.Backend$runMethodRaw$1.invoke(Backend.kt:118)
at net.ankiweb.rsdroid.Backend$runMethodRaw$1.invoke(Backend.kt:117)
at net.ankiweb.rsdroid.Backend.withBackend(Backend.kt:131)
at net.ankiweb.rsdroid.Backend.runMethodRaw(Backend.kt:117)
at anki.backend.GeneratedBackend.openCollectionRaw(GeneratedBackend.kt:102)
at anki.backend.GeneratedBackend.openCollection(GeneratedBackend.kt:109)
at net.ankiweb.rsdroid.Backend.openCollection(Backend.kt:98)
at net.ankiweb.rsdroid.Backend.openCollection(Backend.kt:57)
at com.ichi2.libanki.Storage.openDB$AnkiDroid_playDebug(Storage.kt:52)
at com.ichi2.libanki.Collection.reopen(Collection.kt:206)
at com.ichi2.libanki.Collection.reopen$default(Collection.kt:203)
at com.ichi2.libanki.Collection.<init>(Collection.kt:138)
at com.ichi2.libanki.Storage.collection(Storage.kt:40)
at com.ichi2.anki.CollectionManager.ensureOpenInner(CollectionManager.kt:230)
at com.ichi2.anki.CollectionManager.access$ensureOpenInner(CollectionManager.kt:39)
at com.ichi2.anki.CollectionManager$withCol$2.invoke(CollectionManager.kt:101)
at com.ichi2.anki.CollectionManager$withCol$2.invoke(CollectionManager.kt:100)
at com.ichi2.anki.CollectionManager$withQueue$2.invokeSuspend(CollectionManager.kt:86)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:65)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:371)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:21)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:88)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:123)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:52)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:43)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
at com.ichi2.anki.Reviewer.addFlags(Reviewer.kt:660)
at com.ichi2.anki.Reviewer.onCreateOptionsMenu(Reviewer.kt:677)
at android.app.Activity.$$robo$$android_app_Activity$onCreatePanelMenu(Activity.java:4343)
at android.app.Activity.onCreatePanelMenu(Activity.java)
at androidx.activity.ComponentActivity.onCreatePanelMenu(ComponentActivity.java:520)
at androidx.appcompat.view.WindowCallbackWrapper.onCreatePanelMenu(WindowCallbackWrapper.java:94)
at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.onCreatePanelMenu(AppCompatDelegateImpl.java:3442)
at androidx.appcompat.app.ToolbarActionBar.populateOptionsMenu(ToolbarActionBar.java:458)
at androidx.appcompat.app.ToolbarActionBar$1.run(ToolbarActionBar.java:58)
at android.os.Handler.$$robo$$android_os_Handler$handleCallback(Handler.java:942)
at android.os.Handler.handleCallback(Handler.java)
at android.os.Handler.$$robo$$android_os_Handler$dispatchMessage(Handler.java:99)
at android.os.Handler.dispatchMessage(Handler.java)
at org.robolectric.shadows.ShadowPausedLooper$IdlingRunnable.doRun(ShadowPausedLooper.java:573)
at org.robolectric.shadows.ShadowPausedLooper$ControlRunnable.run(ShadowPausedLooper.java:536)
at org.robolectric.shadows.ShadowPausedLooper.executeOnLooper(ShadowPausedLooper.java:629)
at org.robolectric.shadows.ShadowPausedLooper.idle(ShadowPausedLooper.java:104)
at org.robolectric.shadows.ShadowPausedLooper.idleIfPaused(ShadowPausedLooper.java:177)
at org.robolectric.android.controller.ActivityController.visible(ActivityController.java:232)
at com.ichi2.anki.RobolectricTest$Companion.startActivityNormallyOpenCollectionWithIntent(RobolectricTest.kt:281)
at com.ichi2.anki.RobolectricTest.startActivityNormallyOpenCollectionWithIntent(RobolectricTest.kt)
at com.ichi2.anki.RobolectricTest.startActivityNormallyOpenCollectionWithIntent$AnkiDroid_playDebugUnitTest(RobolectricTest.kt:339)
at com.ichi2.anki.WhiteboardDefaultForegroundColorTest.getForegroundColor(WhiteboardDefaultForegroundColorTest.kt:43)
at com.ichi2.anki.WhiteboardDefaultForegroundColorTest.testDefaultForegroundColor(WhiteboardDefaultForegroundColorTest.kt:38)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:588)
at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$2(SandboxTestRunner.java:290)
at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:101)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
mikehardy commented 2 weeks ago

Things to do to close this:

david-allison commented 2 weeks ago

The following does NOT make tests fail early

Index: AnkiDroid/src/main/java/com/ichi2/libanki/Storage.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.kt
--- a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.kt  (revision 638209ed4989872d528fedd845772b4187d80df2)
+++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.kt  (revision 0daf248ba9a11f454b15ec2dadb783294b84d7c3)
@@ -18,8 +18,10 @@
 import com.ichi2.anki.getDayStart
 import com.ichi2.libanki.utils.Time
 import com.ichi2.libanki.utils.TimeManager.time
+import com.ichi2.utils.isRobolectric
 import net.ankiweb.rsdroid.Backend
 import net.ankiweb.rsdroid.BackendFactory
+import net.ankiweb.rsdroid.exceptions.BackendInvalidInputException.BackendCollectionAlreadyOpenException
 import java.io.File

 object Storage {
@@ -49,7 +51,12 @@
         if (afterFullSync) {
             create = false
         } else {
-            backend.openCollection(if (isInMemory) ":memory:" else path)
+            try {
+                backend.openCollection(if (isInMemory) ":memory:" else path)
+            } catch (e: BackendCollectionAlreadyOpenException) {
+                if (!isRobolectric) throw e
+                throw Error("BackendCollectionAlreadyOpenException", e)
+            }
         }
         val db = DB.withRustBackend(backend)

See: https://github.com/david-allison/Anki-Android/actions/runs/8855405150

david-allison commented 2 weeks ago

Reproduction of hang (runs on macOS)

Index: AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt    (revision 9036f1611380e2952630527dd26483e9d4772805)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt    (date 1714178146303)
@@ -40,7 +40,9 @@
 import androidx.appcompat.widget.Toolbar
 import androidx.core.app.ActivityCompat
 import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import anki.backend.BackendError
 import anki.frontend.SetSchedulingStatesRequest
 import com.google.android.material.color.MaterialColors
 import com.google.android.material.snackbar.Snackbar
@@ -87,6 +89,8 @@
 import com.ichi2.utils.Permissions.canRecordAudio
 import com.ichi2.utils.ViewGroupUtils.setRenderWorkaround
 import com.ichi2.widget.WidgetStatus.updateInBackground
+import kotlinx.coroutines.launch
+import net.ankiweb.rsdroid.exceptions.BackendInvalidInputException
 import timber.log.Timber
 import java.io.File

@@ -682,6 +686,9 @@
     @NeedsTest("Order of operations needs Testing around Menu (Overflow) Icons and their colors.")
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
         Timber.d("onCreateOptionsMenu()")
+        lifecycleScope.launch {
+            throw BackendInvalidInputException.BackendCollectionAlreadyOpenException(BackendError.getDefaultInstance())
+        }
         // NOTE: This is called every time a new question is shown via invalidate options menu
         menuInflater.inflate(R.menu.reviewer, menu)
         displayIcons(menu)
david-allison commented 2 weeks ago

The hang appears to be: https://github.com/ACRA/acra/blob/0c2b36ceb873f75b7528cd76f2fefec3f0d5ac23/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.kt#L50

Caused by an uncaught exception (setDefaultUncaughtExceptionHandler)

david-allison commented 2 weeks ago

Applied a fix, we'll know for certain: https://github.com/david-allison/Anki-Android/actions/runs/8859500839/job/24329333389

david-allison commented 2 weeks ago

Proposal on the anti-flake script: fail if the branch is main.

Avoids user error when forgetting to select a branch

mikehardy commented 2 weeks ago

Proposal on the anti-flake script: fail if the branch is main.

Avoids user error when forgetting to select a branch

But...I want to run it on main sometimes? If a flake slips through we will want to run it on main to verify ?

david-allison commented 1 week ago

Any way we can confirm that we want to run it on main?

I currently have "ad blindness" when it comes to branch selection, this might go away with time now it's a necessary field to change