Merge #15e2902d: Ignore platform finalizer-watchdog crashes in crash re…

Ignore platform finalizer-watchdog crashes in crash reports

nostr:nevent1qqsptc5s9kvxe6ahr4yxj6cc7ur8sgg03tu7epn0hyz05s7m248uedqpz3mhxue69uhhyetvv9ujumn8d96zuer9wc5kul9c

PR-Author: greenart7c3
nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5

PR description:

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.
This commit is contained in:
greenart7c3
2026-07-31 07:05:04 -03:00
2 changed files with 73 additions and 2 deletions
@@ -20,6 +20,7 @@
*/ */
package com.greenart7c3.nostrsigner.service.crashreports package com.greenart7c3.nostrsigner.service.crashreports
import java.util.concurrent.TimeoutException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -33,12 +34,29 @@ class UnexpectedCrashSaver(
t: Thread, t: Thread,
e: Throwable, e: Throwable,
) { ) {
if (e !is OutOfMemoryError) { if (!isJunkReport(e)) {
// OOM reports are junk
scope.launch { scope.launch {
cache.writeReport(ReportAssembler().buildReport(e)) cache.writeReport(ReportAssembler().buildReport(e))
} }
} }
defaultUEH!!.uncaughtException(t, 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") }
)
}
} }
@@ -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")))
}
}