Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block reports sent to ACRA #17402

Merged
merged 5 commits into from
Dec 2, 2024
Merged

Block reports sent to ACRA #17402

merged 5 commits into from
Dec 2, 2024

Conversation

voczi
Copy link
Contributor

@voczi voczi commented Nov 9, 2024

See title. Related:
#17392
ankidroid/Anki-Android-Backend#439
ankitects/anki@ba1f5f4

Preemptive "may lord have mercy".

@voczi voczi added Review High Priority Request for high priority review Needs Review labels Nov 9, 2024
mikehardy

This comment was marked as outdated.

@voczi voczi requested a review from mikehardy November 9, 2024 14:52
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Architecture:

  • This doesn't process logcat
  • I believe this changes the type of the exception to Throwable

@mikehardy
Copy link
Member

with regard to "This doesn't process logcat" from David -> I noticed while verifying my acrarium purge that there were some instances where the stack prompting a current error did not contain an email but the included logcat did. So it is a valid concern. I altered my purge matcher to also match emails deeper into the report (logcat also, not just original stack) so I'm cleaning them now, but better if they never hit logcat. Whole thing is messy, for sure

@voczi
Copy link
Contributor Author

voczi commented Nov 9, 2024

Work's left to be done. Still unsure about certain things.

@voczi voczi added Needs Author Reply Waiting for a reply from the original author and removed Review High Priority Request for high priority review Needs Review labels Nov 9, 2024
@voczi voczi changed the title Filter/block reports sent to ACRA Block reports sent to ACRA Nov 10, 2024
@voczi voczi added Needs Review and removed Needs Author Reply Waiting for a reply from the original author labels Nov 10, 2024
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Looks SO much better, thanks so much!!!

One suggestion, take it or leave it

@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge and removed Needs Review labels Nov 10, 2024
@voczi voczi added the squash-merge The pull request currently requires maintainers to "Squash Merge" label Nov 10, 2024
david-allison
david-allison previously approved these changes Nov 10, 2024
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Approval still stands.

Important

squash-merge The pull request currently requires maintainers to "Squash Merge"

@david-allison david-allison dismissed their stale review November 11, 2024 01:33

Given discussion on Discord - pushing this back to also be implemented for unhandled exceptions

@lukstbit lukstbit added the Needs Author Reply Waiting for a reply from the original author label Nov 11, 2024
@mikehardy

This comment was marked as resolved.

This comment was marked as outdated.

@github-actions github-actions bot added the Stale label Nov 26, 2024
@mikehardy
Copy link
Member

mikehardy commented Nov 26, 2024

No idea what's taking so long (about anki upstream release, that is), but anyway 24.11rc2 just released and I'm going to publish it soon on backend
I think the last thing to do here is to hook into the exception handling chain to swallow unhandled ones, an important-but-hard addition

@github-actions github-actions bot removed the Stale label Nov 26, 2024
@mikehardy mikehardy force-pushed the acra-filter branch 2 times, most recently from 8a2f840 to e68c8fc Compare November 30, 2024 20:27
Copy link
Member

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

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

I implemented the handling of uncaught exceptions as well, what do you guys think?
I thought the isPII filter and initial test were great, so I'm approving those
Happy to bend the last commit around to whatever seems best to get it in

@mikehardy mikehardy removed the Needs Author Reply Waiting for a reply from the original author label Nov 30, 2024
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Looks good! Two questions

I had a play with Default interface implementations. I didn't like this and wouldn't recommend it, as it makes more methods public than there should be, but it is less code

Patch
Subject: [PATCH] refactor: extract dialog fragments from Anki[Activity/Fragment]

* dismissAllDialogFragments
* showDialogFragment

**AnkiFragment**
* showDialogFragment

This affects all callers, as we now use extension methods
---
Index: AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/ThrowableFilterService.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/ThrowableFilterService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/ThrowableFilterService.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/ThrowableFilterService.kt	(revision e68c8fc839fbc5a07dbce6343bc982094a928d15)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/ThrowableFilterService.kt	(date 1733018370799)
@@ -16,71 +16,38 @@
  ****************************************************************************************/
 package com.ichi2.anki.servicelayer
 
-import androidx.annotation.VisibleForTesting
+import com.ichi2.anki.exception.ChainedUncaughtExceptionHandler
+import com.ichi2.anki.exception.Handled
 import net.ankiweb.rsdroid.exceptions.BackendSyncException.BackendSyncServerMessageException
 import timber.log.Timber
 
-object ThrowableFilterService {
-
-    @FunctionalInterface
-    fun interface FilteringExceptionHandler : Thread.UncaughtExceptionHandler
+object ThrowableFilterService : ChainedUncaughtExceptionHandler {
+    override var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
 
-    @VisibleForTesting
-    var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
-
-    var uncaughtExceptionHandler = FilteringExceptionHandler {
-            thread: Thread?, throwable: Throwable ->
+    override fun handleUncaughtException(thread: Thread?, throwable: Throwable): Handled {
         if (thread == null) {
             Timber.w("unexpected: thread was null")
-            return@FilteringExceptionHandler
+            return Handled.NO_CHAIN
         }
         if (shouldDiscardThrowable(throwable)) {
             Timber.i("discarding throwable")
-            return@FilteringExceptionHandler
+            Handled.NO_CHAIN
         }
-        originalUncaughtExceptionHandler?.uncaughtException(thread, throwable)
+        return Handled.CHAIN
     }
 
     fun initialize() {
-        Timber.i("initialize()")
         installDefaultExceptionHandler()
     }
 
-    /**
-     * We want to filter any exceptions with PII, then chain to other handlers
-     */
-    @Synchronized
-    @VisibleForTesting
-    fun installDefaultExceptionHandler() {
-        originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
-        Timber.d("Chaining to uncaughtExceptionHandler (%s)", originalUncaughtExceptionHandler)
-        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler)
-    }
-
-    /**
-     * Reset the default exception handler
-     */
-    @Synchronized
-    @VisibleForTesting
-    fun unInstallDefaultExceptionHandler() {
-        Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
-        originalUncaughtExceptionHandler = null
-    }
-
     /**
      * Re-Initialize the throwable filter
      */
-    @Synchronized
     fun reInitialize() {
-        // send any pending async hits, re-chain default exception handlers and re-init
-        Timber.i("reInitialize()")
-        unInstallDefaultExceptionHandler()
-        initialize()
+        reInitializeDefaultExceptionHandler()
     }
 
-    fun shouldDiscardThrowable(t: Throwable): Boolean {
-        return !t.safeFromPII()
-    }
+    fun shouldDiscardThrowable(t: Throwable) = !t.safeFromPII()
 
     /**
      * Checks if the [Throwable] is safe from Personally Identifiable Information (PII)
Index: AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt
--- a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt	(revision e68c8fc839fbc5a07dbce6343bc982094a928d15)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.kt	(date 1733018083962)
@@ -29,6 +29,8 @@
 import com.ichi2.anki.AnkiDroidApp
 import com.ichi2.anki.BuildConfig
 import com.ichi2.anki.R
+import com.ichi2.anki.exception.ChainedUncaughtExceptionHandler
+import com.ichi2.anki.exception.Handled
 import com.ichi2.anki.preferences.sharedPrefs
 import com.ichi2.utils.DisplayUtils
 import com.ichi2.utils.KotlinCleanup
@@ -38,12 +40,12 @@
 import timber.log.Timber
 
 @KotlinCleanup("see if we can make variables lazy, or properties without the `s` prefix")
-object UsageAnalytics {
+object UsageAnalytics : ChainedUncaughtExceptionHandler {
     const val ANALYTICS_OPTIN_KEY = "analytics_opt_in"
 
     @KotlinCleanup("lateinit")
     private var sAnalytics: GoogleAnalytics? = null
-    private var sOriginalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
+    override var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
     private var sOptIn = false
     private var sAnalyticsTrackingId: String? = null
     private var sAnalyticsSamplePercentage = -1
@@ -51,14 +53,13 @@
     @FunctionalInterface
     fun interface AnalyticsLoggingExceptionHandler : Thread.UncaughtExceptionHandler
 
-    var uncaughtExceptionHandler = AnalyticsLoggingExceptionHandler {
-            thread: Thread?, throwable: Throwable ->
+    override fun handleUncaughtException(thread: Thread?, throwable: Throwable): Handled {
         sendAnalyticsException(throwable, true)
         if (thread == null) {
             Timber.w("unexpected: thread was null")
-            return@AnalyticsLoggingExceptionHandler
+            return Handled.NO_CHAIN
         }
-        sOriginalUncaughtExceptionHandler!!.uncaughtException(thread, throwable)
+        return Handled.CHAIN
     }
 
     /**
@@ -127,27 +128,6 @@
         reInitialize()
     }
 
-    /**
-     * We want to send an analytics hit on any exception, then chain to other handlers (e.g., ACRA)
-     */
-    @Synchronized
-    @VisibleForTesting
-    fun installDefaultExceptionHandler() {
-        sOriginalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
-        Timber.d("Chaining to uncaughtExceptionHandler (%s)", sOriginalUncaughtExceptionHandler)
-        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler)
-    }
-
-    /**
-     * Reset the default exception handler
-     */
-    @Synchronized
-    @VisibleForTesting
-    fun unInstallDefaultExceptionHandler() {
-        Thread.setDefaultUncaughtExceptionHandler(sOriginalUncaughtExceptionHandler)
-        sOriginalUncaughtExceptionHandler = null
-    }
-
     /**
      * Allow users to enable or disable analytics
      */
Index: AnkiDroid/src/main/java/com/ichi2/anki/exception/ChainedUncaughtExceptionHandler.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/exception/ChainedUncaughtExceptionHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/exception/ChainedUncaughtExceptionHandler.kt
new file mode 100644
--- /dev/null	(date 1733018290159)
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/exception/ChainedUncaughtExceptionHandler.kt	(date 1733018290159)
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (c) 2024 David Allison <[email protected]>
+ *
+ *  This program is free software; you can redistribute it and/or modify it under
+ *  the terms of the GNU General Public License as published by the Free Software
+ *  Foundation; either version 3 of the License, or (at your option) any later
+ *  version.
+ *
+ *  This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *  PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along with
+ *  this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.ichi2.anki.exception
+
+import androidx.annotation.CheckResult
+import androidx.annotation.VisibleForTesting
+import timber.log.Timber
+
+enum class Handled {
+    NO_CHAIN,
+    CHAIN
+}
+
+interface ChainedUncaughtExceptionHandler {
+    @CheckResult
+    fun handleUncaughtException(thread: Thread?, throwable: Throwable) : Handled
+
+    /* Implementation */
+
+    var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler?
+
+    /**
+     * We want to send an analytics hit on any exception, then chain to other handlers (e.g., ACRA)
+     */
+    @VisibleForTesting
+    fun installDefaultExceptionHandler() {
+        Timber.i("installDefaultExceptionHandler")
+        synchronized(this) {
+            originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+            Timber.d("Chaining to uncaughtExceptionHandler (%s)", originalUncaughtExceptionHandler)
+            Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler)
+        }
+    }
+
+    /**
+     * Reset the default exception handler
+     */
+    @VisibleForTesting
+    fun unInstallDefaultExceptionHandler() {
+        synchronized(this) {
+            Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
+            originalUncaughtExceptionHandler = null
+        }
+    }
+
+    fun reInitializeDefaultExceptionHandler() {
+        Timber.i("reInitializeDefaultExceptionHandler()")
+        synchronized(this) {
+            unInstallDefaultExceptionHandler()
+            installDefaultExceptionHandler()
+        }
+    }
+
+    val uncaughtExceptionHandler: Thread.UncaughtExceptionHandler
+        get() = Thread.UncaughtExceptionHandler { thread: Thread?, throwable: Throwable ->
+            when (handleUncaughtException(thread, throwable)) {
+                Handled.NO_CHAIN -> return@UncaughtExceptionHandler
+                Handled.CHAIN -> { }
+            }
+            if (thread == null) {
+                Timber.w("unexpected: thread was null")
+                return@UncaughtExceptionHandler
+            }
+            originalUncaughtExceptionHandler!!.uncaughtException(thread, throwable)
+        }
+}
\ No newline at end of file

@mikehardy
Copy link
Member

If macOS unit test fails, and it seems like it might with a current unrelated heapspace problem, contemplating testing one of these things -

1- changing these params on test forking behavior:

it.maxParallelForks = gradleTestMaxParallelForks
it.forkEvery = 40
it.systemProperties["junit.jupiter.execution.parallel.enabled"] = true
it.systemProperties["junit.jupiter.execution.parallel.mode.default"] = "concurrent"

2- bumping up heapspace 🤷

voczi and others added 4 commits December 1, 2024 19:00
We want to filter all exception - that is ones that are uncaught as well
as ones that are imperatively sent through CrashReportService

That requires attaching filter to out Thread.defaultExceptionHandler chain

- extract filter into service, use it from crash report service
- implement default exception handler handling, init/reinit as needed
- concrete types for the handlers and public uninstall/install for testability
@mikehardy mikehardy merged commit 333ef64 into ankidroid:main Dec 2, 2024
9 checks passed
@github-actions github-actions bot added this to the 2.20 Release milestone Dec 2, 2024
@github-actions github-actions bot removed Needs Second Approval Has one approval, one more approval to merge squash-merge The pull request currently requires maintainers to "Squash Merge" labels Dec 2, 2024
@mikehardy
Copy link
Member

Fixed the macOS runner issue as well (test executors needed more heap)
And this has been reviewed to death, reanimated, then reviewed to death again 😆 merged

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.

4 participants