Skip to content

Commit 3baa73f

Browse files
authored
Fix native-only thread dump parsing and tracingContext for ANRv2 (#2839)
1 parent 26bc310 commit 3baa73f

File tree

8 files changed

+1379
-19
lines changed

8 files changed

+1379
-19
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Fixes
6+
7+
- Fix ANRv2 thread dump parsing for native-only threads ([#2839](https://github.com/getsentry/sentry-java/pull/2839))
8+
- Derive `TracingContext` values from event for ANRv2 events ([#2839](https://github.com/getsentry/sentry-java/pull/2839))
9+
310
## 6.25.2
411

512
### Fixes

sentry-android-core/src/main/java/io/sentry/android/core/internal/threaddump/ThreadDumpParser.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,35 @@
3838
public class ThreadDumpParser {
3939
private static final Pattern BEGIN_MANAGED_THREAD_RE =
4040
Pattern.compile("\"(.*)\" (.*) ?prio=(\\d+)\\s+tid=(\\d+)\\s*(.*)");
41+
42+
private static final Pattern BEGIN_UNMANAGED_NATIVE_THREAD_RE =
43+
Pattern.compile("\"(.*)\" (.*) ?sysTid=(\\d+)");
44+
4145
private static final Pattern NATIVE_RE =
4246
Pattern.compile(
43-
" (?:native: )?#\\d+ \\S+ [0-9a-fA-F]+\\s+(.*?)\\s+\\((.*)\\+(\\d+)\\)(?: \\(.*\\))?");
47+
" *(?:native: )?#\\d+ \\S+ [0-9a-fA-F]+\\s+(.*?)\\s+\\((.*)\\+(\\d+)\\)(?: \\(.*\\))?");
4448
private static final Pattern NATIVE_NO_LOC_RE =
4549
Pattern.compile(
46-
" (?:native: )?#\\d+ \\S+ [0-9a-fA-F]+\\s+(.*)\\s*\\(?(.*)\\)?(?: \\(.*\\))?");
50+
" *(?:native: )?#\\d+ \\S+ [0-9a-fA-F]+\\s+(.*)\\s*\\(?(.*)\\)?(?: \\(.*\\))?");
4751
private static final Pattern JAVA_RE =
48-
Pattern.compile(" at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\((.*):([\\d-]+)\\)");
52+
Pattern.compile(" *at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\((.*):([\\d-]+)\\)");
4953
private static final Pattern JNI_RE =
50-
Pattern.compile(" at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\(Native method\\)");
54+
Pattern.compile(" *at (?:(.+)\\.)?([^.]+)\\.([^.]+)\\(Native method\\)");
5155
private static final Pattern LOCKED_RE =
52-
Pattern.compile(" - locked \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
56+
Pattern.compile(" *- locked \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
5357
private static final Pattern SLEEPING_ON_RE =
54-
Pattern.compile(" - sleeping on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
58+
Pattern.compile(" *- sleeping on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
5559
private static final Pattern WAITING_ON_RE =
56-
Pattern.compile(" - waiting on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
60+
Pattern.compile(" *- waiting on \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
5761
private static final Pattern WAITING_TO_LOCK_RE =
5862
Pattern.compile(
59-
" - waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
63+
" *- waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)");
6064
private static final Pattern WAITING_TO_LOCK_HELD_RE =
6165
Pattern.compile(
62-
" - waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"
66+
" *- waiting to lock \\<([0x0-9a-fA-F]{1,16})\\> \\(a (?:(.+)\\.)?([^.]+)\\)"
6367
+ "(?: held by thread (\\d+))");
6468
private static final Pattern WAITING_TO_LOCK_UNKNOWN_RE =
65-
Pattern.compile(" - waiting to lock an unknown object");
69+
Pattern.compile(" *- waiting to lock an unknown object");
6670
private static final Pattern BLANK_RE = Pattern.compile("\\s+");
6771

6872
private final @NotNull SentryOptions options;
@@ -82,6 +86,7 @@ public List<SentryThread> parse(final @NotNull Lines lines) {
8286
final List<SentryThread> sentryThreads = new ArrayList<>();
8387

8488
final Matcher beginManagedThreadRe = BEGIN_MANAGED_THREAD_RE.matcher("");
89+
final Matcher beginUnmanagedNativeThreadRe = BEGIN_UNMANAGED_NATIVE_THREAD_RE.matcher("");
8590

8691
while (lines.hasNext()) {
8792
final Line line = lines.next();
@@ -92,7 +97,7 @@ public List<SentryThread> parse(final @NotNull Lines lines) {
9297
final String text = line.text;
9398
// we only handle managed threads, as unmanaged/not attached do not have the thread id and
9499
// our protocol does not support this case
95-
if (matches(beginManagedThreadRe, text)) {
100+
if (matches(beginManagedThreadRe, text) || matches(beginUnmanagedNativeThreadRe, text)) {
96101
lines.rewind();
97102

98103
final SentryThread thread = parseThread(lines);
@@ -108,6 +113,7 @@ private SentryThread parseThread(final @NotNull Lines lines) {
108113
final SentryThread sentryThread = new SentryThread();
109114

110115
final Matcher beginManagedThreadRe = BEGIN_MANAGED_THREAD_RE.matcher("");
116+
final Matcher beginUnmanagedNativeThreadRe = BEGIN_UNMANAGED_NATIVE_THREAD_RE.matcher("");
111117

112118
// thread attributes
113119
if (!lines.hasNext()) {
@@ -137,14 +143,24 @@ private SentryThread parseThread(final @NotNull Lines lines) {
137143
sentryThread.setState(state);
138144
}
139145
}
140-
final String threadName = sentryThread.getName();
141-
if (threadName != null) {
142-
final boolean isMain = threadName.equals("main");
143-
sentryThread.setMain(isMain);
144-
// since it's an ANR, the crashed thread will always be main
145-
sentryThread.setCrashed(isMain);
146-
sentryThread.setCurrent(isMain && !isBackground);
146+
} else if (matches(beginUnmanagedNativeThreadRe, line.text)) {
147+
final Long sysTid = getLong(beginUnmanagedNativeThreadRe, 3, null);
148+
if (sysTid == null) {
149+
options.getLogger().log(SentryLevel.DEBUG, "No thread id in the dump, skipping thread.");
150+
// tid is required by our protocol
151+
return null;
147152
}
153+
sentryThread.setId(sysTid);
154+
sentryThread.setName(beginUnmanagedNativeThreadRe.group(1));
155+
}
156+
157+
final String threadName = sentryThread.getName();
158+
if (threadName != null) {
159+
final boolean isMain = threadName.equals("main");
160+
sentryThread.setMain(isMain);
161+
// since it's an ANR, the crashed thread will always be main
162+
sentryThread.setCrashed(isMain);
163+
sentryThread.setCurrent(isMain && !isBackground);
148164
}
149165

150166
// thread stacktrace

sentry-android-core/src/test/java/io/sentry/android/core/internal/threaddump/ThreadDumpParserTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import java.io.File
66
import kotlin.test.Test
77
import kotlin.test.assertEquals
88
import kotlin.test.assertNotNull
9+
import kotlin.test.assertNull
910

1011
class ThreadDumpParserTest {
1112

@@ -72,4 +73,26 @@ class ThreadDumpParserTest {
7273
assertEquals(67, firstFrame.lineno)
7374
assertEquals(null, firstFrame.isInApp)
7475
}
76+
77+
@Test
78+
fun `parses native only thread dump`() {
79+
val lines = Lines.readLines(File("src/test/resources/thread_dump_native_only.txt"))
80+
val parser = ThreadDumpParser(
81+
SentryOptions().apply { addInAppInclude("io.sentry.samples") },
82+
false
83+
)
84+
val threads = parser.parse(lines)
85+
// just verifying a few important threads, as there are many
86+
val thread = threads.find { it.name == "samples.android" }
87+
assertEquals(9955, thread!!.id)
88+
assertNull(thread.state)
89+
assertEquals(false, thread.isCrashed)
90+
assertEquals(false, thread.isMain)
91+
assertEquals(false, thread.isCurrent)
92+
val lastFrame = thread.stacktrace!!.frames!!.last()
93+
assertEquals("/apex/com.android.runtime/lib64/bionic/libc.so", lastFrame.`package`)
94+
assertEquals("syscall", lastFrame.function)
95+
assertEquals(28, lastFrame.lineno)
96+
assertNull(lastFrame.isInApp)
97+
}
7598
}

0 commit comments

Comments
 (0)