diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fe301a349..6a83f72f168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Fix main thread identification for tombstone (native crash) events ([#5742](https://github.com/getsentry/sentry-java/pull/5742)) + ### Dependencies - Bump Native SDK from v0.15.2 to v0.15.3 ([#5728](https://github.com/getsentry/sentry-java/pull/5728)) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java index 1f142b52c9a..c3966615899 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/tombstone/TombstoneParser.java @@ -114,6 +114,15 @@ private List createThreads( // the backend currently requires a stack-trace in exception exc.setStacktrace(stacktrace); } + + // thread id always equals the process id, + // so we use it to reliably detect the main thread + if (tombstone.pid == threadEntryValue.id) { + // the OS may provide a (truncated) process name; normalize it + // back to "main" so downstream consumers see a consistent name + thread.setName("main"); + thread.setMain(true); + } threads.add(thread); } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt index 9890d553dbc..e3e88d04f7a 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt @@ -75,7 +75,8 @@ class TombstoneIntegrationTest : ApplicationExitIntegrationTestBase thread.id == crashedThreadId } - assertEquals("samples.android", crashedThread!!.name) + assertEquals("main", crashedThread!!.name) + assertTrue(crashedThread.isMain!!) assertTrue(crashedThread.isCrashed!!) // Verify that frames from the app's native library are marked as in-app diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt index 34e704188c4..70fc48fd9be 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt @@ -12,6 +12,7 @@ import java.io.StringWriter import java.util.zip.GZIPInputStream import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import org.mockito.kotlin.mock @@ -99,6 +100,10 @@ class TombstoneParserTest { // threads assertEquals(62, event.threads!!.size) + val mainThread = event.threads!!.single { it.isMain == true } + assertEquals(21891, mainThread.id) + assertEquals("main", mainThread.name) + for (thread in event.threads!!) { assertNotNull(thread.id) if (thread.id == crashedThreadId) { @@ -397,6 +402,73 @@ class TombstoneParserTest { assertEquals(expectedJson, actualJson) } + @Test + fun `identifies the main thread via pid matching the thread id and normalizes its name`() { + val tombstone = + Tombstone.Builder() + .pid(1000) + .tid(2000) + .signal(Signal(11, "SIGSEGV", 1, "SEGV_MAPERR", false, 0, 0, false, 0, null)) + // main thread: id == pid, but the OS renamed it to the process name + .addThread( + TombstoneThread( + 1000, + "io.sentry.samples.android", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x100, 0, "main", 0, "/system/lib64/libc.so", 0, "")), + emptyList(), + 0, + 0, + ) + ) + .addThread( + TombstoneThread( + 2000, + "crashed-worker", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x200, 0, "crash", 0, "/system/lib64/libc.so", 0, "")), + emptyList(), + 0, + 0, + ) + ) + .addThread( + TombstoneThread( + 3000, + "Thread-3", + emptyList(), + emptyList(), + emptyList(), + listOf(BacktraceFrame(0, 0x300, 0, "work", 0, "/system/lib64/libc.so", 0, "")), + emptyList(), + 0, + 0, + ) + ) + .build() + + val event = parser.parse(tombstone) + val threads = event.threads!! + + val main = threads.single { it.isMain == true } + assertEquals(1000, main.id) + assertEquals("main", main.name) + + val crashed = threads.single { it.isCrashed == true } + assertEquals(2000, crashed.id) + assertNotEquals(true, crashed.isMain) + assertEquals("crashed-worker", crashed.name) + + val background = threads.single { it.id == 3000L } + assertNotEquals(true, background.isMain) + assertNotEquals(true, background.isCrashed) + assertEquals("Thread-3", background.name) + } + @Test fun `parses tombstone when nativeLibraryDir is null`() { val tombstoneStream =