From 86ab6e86e6d25c98ac7a23acdd055dfde6270ee6 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Fri, 31 Jul 2026 06:55:48 -0300 Subject: [PATCH] Ignore platform finalizer-watchdog crashes in crash reports BinderInternal$GcWatcher.finalize() TimeoutExceptions are a known AOSP issue raised by the finalizer watchdog on the FinalizerDaemon thread. They contain no app frames and cannot be caught or prevented by app code, so the report only nags users with an unactionable "send crash report?" prompt. Extend the existing OOM junk filter in UnexpectedCrashSaver to drop TimeoutExceptions whose message or stack matches the finalizer watchdog path. App-thrown TimeoutExceptions and all other crashes are still reported. --- .../crashreports/UnexpectedCrashSaver.kt | 22 +++++++- .../crashreports/UnexpectedCrashSaverTest.kt | 53 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaverTest.kt diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaver.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaver.kt index 1ad1d0ca..07b116d6 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaver.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaver.kt @@ -20,6 +20,7 @@ */ package com.greenart7c3.nostrsigner.service.crashreports +import java.util.concurrent.TimeoutException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -33,12 +34,29 @@ class UnexpectedCrashSaver( t: Thread, e: Throwable, ) { - if (e !is OutOfMemoryError) { - // OOM reports are junk + if (!isJunkReport(e)) { scope.launch { cache.writeReport(ReportAssembler().buildReport(e)) } } defaultUEH!!.uncaughtException(t, e) } + + companion object { + /** + * Crashes we never prompt the user to report because nobody can act on them: + * - OutOfMemoryError: junk reports. + * - Platform finalizer-watchdog timeouts (e.g. + * `BinderInternal$GcWatcher.finalize() timed out after 10 seconds`): a known + * AOSP issue raised on the FinalizerDaemon thread with no app frames. The OS + * kills the process regardless; app code cannot catch, prevent, or fix it. + */ + fun isJunkReport(e: Throwable): Boolean = e is OutOfMemoryError || isPlatformFinalizerTimeout(e) + + private fun isPlatformFinalizerTimeout(e: Throwable): Boolean = e is TimeoutException && + ( + e.message?.contains("finalize() timed out") == true || + e.stackTrace.any { it.className.startsWith("java.lang.Daemons\$FinalizerDaemon") } + ) + } } diff --git a/app/src/test/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaverTest.kt b/app/src/test/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaverTest.kt new file mode 100644 index 00000000..aa8fbfb7 --- /dev/null +++ b/app/src/test/java/com/greenart7c3/nostrsigner/service/crashreports/UnexpectedCrashSaverTest.kt @@ -0,0 +1,53 @@ +package com.greenart7c3.nostrsigner.service.crashreports + +import java.util.concurrent.TimeoutException +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class UnexpectedCrashSaverTest { + private fun finalizerWatchdogCrash(): TimeoutException { + val e = TimeoutException("com.android.internal.os.BinderInternal\$GcWatcher.finalize() timed out after 10 seconds") + e.stackTrace = + arrayOf( + StackTraceElement("com.android.internal.os.BinderInternal\$GcWatcher", "finalize", "BinderInternal.java", 64), + StackTraceElement("java.lang.Daemons\$FinalizerDaemon", "doFinalize", "Daemons.java", 389), + StackTraceElement("java.lang.Daemons\$FinalizerDaemon", "processReference", "Daemons.java", 369), + StackTraceElement("java.lang.Daemons\$FinalizerDaemon", "runInternal", "Daemons.java", 354), + StackTraceElement("java.lang.Daemons\$Daemon", "run", "Daemons.java", 135), + StackTraceElement("java.lang.Thread", "run", "Thread.java", 1564), + ) + return e + } + + @Test + fun `binder gc watcher finalizer timeout is junk`() { + assertTrue(UnexpectedCrashSaver.isJunkReport(finalizerWatchdogCrash())) + } + + @Test + fun `finalizer daemon stack is junk even without the message`() { + val e = TimeoutException(null as String?) + e.stackTrace = + arrayOf( + StackTraceElement("android.os.BinderProxy", "finalize", "BinderProxy.java", 100), + StackTraceElement("java.lang.Daemons\$FinalizerDaemon", "doFinalize", "Daemons.java", 389), + ) + assertTrue(UnexpectedCrashSaver.isJunkReport(e)) + } + + @Test + fun `out of memory is junk`() { + assertTrue(UnexpectedCrashSaver.isJunkReport(OutOfMemoryError())) + } + + @Test + fun `app timeout exception is not junk`() { + assertFalse(UnexpectedCrashSaver.isJunkReport(TimeoutException("socket timed out"))) + } + + @Test + fun `regular crash is not junk`() { + assertFalse(UnexpectedCrashSaver.isJunkReport(IllegalStateException("boom"))) + } +}