Skip to content

Commit cb27996

Browse files
adinauerphilipphofmannmarkushigetsentry-botgetsentry-bot
authored
Merge hotfix 6.4.4 back into main (#2283)
Co-authored-by: Philipp Hofmann <[email protected]> Co-authored-by: Alexander Dinauer <[email protected]> Co-authored-by: Markus Hintersteiner <[email protected]> Co-authored-by: Markus Hintersteiner <[email protected]> Co-authored-by: getsentry-bot <[email protected]> Co-authored-by: getsentry-bot <[email protected]>
1 parent ece81f7 commit cb27996

File tree

4 files changed

+103
-17
lines changed

4 files changed

+103
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727

2828
- Server-Side Dynamic Sampling Context support ([#2226](https://github.com/getsentry/sentry-java/pull/2226))
2929

30+
## 6.4.4
31+
32+
### Fixes
33+
34+
- Fix ConcurrentModificationException due to FrameMetricsAggregator manipulation ([#2282](https://github.com/getsentry/sentry-java/pull/2282))
3035

3136
## 6.4.3
3237

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
public final class io/sentry/android/core/ActivityFramesTracker {
22
public fun <init> (Lio/sentry/android/core/LoadClass;)V
33
public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;)V
4+
public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/ILogger;Lio/sentry/android/core/MainLooperHandler;)V
45
public fun addActivity (Landroid/app/Activity;)V
56
public fun setMetrics (Landroid/app/Activity;Lio/sentry/protocol/SentryId;)V
67
public fun stop ()V

sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import androidx.core.app.FrameMetricsAggregator;
66
import io.sentry.ILogger;
77
import io.sentry.MeasurementUnit;
8+
import io.sentry.SentryLevel;
9+
import io.sentry.android.core.internal.util.MainThreadChecker;
810
import io.sentry.protocol.MeasurementValue;
911
import io.sentry.protocol.SentryId;
1012
import java.util.HashMap;
@@ -30,21 +32,37 @@ public final class ActivityFramesTracker {
3032
private final @NotNull Map<Activity, FrameCounts> frameCountAtStartSnapshots =
3133
new WeakHashMap<>();
3234

33-
public ActivityFramesTracker(final @NotNull LoadClass loadClass, final @Nullable ILogger logger) {
35+
private final @Nullable ILogger logger;
36+
private final @NotNull MainLooperHandler handler;
37+
38+
public ActivityFramesTracker(
39+
final @NotNull LoadClass loadClass,
40+
final @Nullable ILogger logger,
41+
final @NotNull MainLooperHandler handler) {
3442
androidXAvailable =
3543
loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", logger);
3644
if (androidXAvailable) {
3745
frameMetricsAggregator = new FrameMetricsAggregator();
3846
}
47+
this.logger = logger;
48+
this.handler = handler;
49+
}
50+
51+
public ActivityFramesTracker(final @NotNull LoadClass loadClass, final @Nullable ILogger logger) {
52+
this(loadClass, logger, new MainLooperHandler());
3953
}
4054

4155
public ActivityFramesTracker(final @NotNull LoadClass loadClass) {
4256
this(loadClass, null);
4357
}
4458

4559
@TestOnly
46-
ActivityFramesTracker(final @Nullable FrameMetricsAggregator frameMetricsAggregator) {
60+
ActivityFramesTracker(
61+
final @Nullable FrameMetricsAggregator frameMetricsAggregator,
62+
final @NotNull MainLooperHandler handler) {
4763
this.frameMetricsAggregator = frameMetricsAggregator;
64+
this.logger = null;
65+
this.handler = handler;
4866
}
4967

5068
private boolean isFrameMetricsAggregatorAvailable() {
@@ -56,7 +74,8 @@ public synchronized void addActivity(final @NotNull Activity activity) {
5674
if (!isFrameMetricsAggregatorAvailable()) {
5775
return;
5876
}
59-
frameMetricsAggregator.add(activity);
77+
78+
runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add");
6079
snapshotFrameCountsAtStart(activity);
6180
}
6281

@@ -75,6 +94,7 @@ private void snapshotFrameCountsAtStart(final @NotNull Activity activity) {
7594
if (frameMetricsAggregator == null) {
7695
return null;
7796
}
97+
7898
final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics();
7999

80100
int totalFrames = 0;
@@ -110,18 +130,18 @@ public synchronized void setMetrics(
110130
return;
111131
}
112132

113-
try {
114-
// NOTE: removing an activity does not reset the frame counts, only reset() does
115-
frameMetricsAggregator.remove(activity);
116-
} catch (Throwable ignored) {
117-
// throws IllegalArgumentException when attempting to remove OnFrameMetricsAvailableListener
118-
// that was never added.
119-
// there's no contains method.
120-
// throws NullPointerException when attempting to remove OnFrameMetricsAvailableListener and
121-
// there was no
122-
// Observers, See
123-
// https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b
124-
}
133+
// NOTE: removing an activity does not reset the frame counts, only reset() does
134+
// throws IllegalArgumentException when attempting to remove
135+
// OnFrameMetricsAvailableListener
136+
// that was never added.
137+
// there's no contains method.
138+
// throws NullPointerException when attempting to remove
139+
// OnFrameMetricsAvailableListener and
140+
// there was no
141+
// Observers, See
142+
// https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b
143+
runSafelyOnUiThread(
144+
() -> frameMetricsAggregator.remove(activity), "FrameMetricsAggregator.remove");
125145

126146
final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity);
127147

@@ -180,12 +200,35 @@ public synchronized void setMetrics(
180200
@SuppressWarnings("NullAway")
181201
public synchronized void stop() {
182202
if (isFrameMetricsAggregatorAvailable()) {
183-
frameMetricsAggregator.stop();
203+
runSafelyOnUiThread(() -> frameMetricsAggregator.stop(), "FrameMetricsAggregator.stop");
184204
frameMetricsAggregator.reset();
185205
}
186206
activityMeasurements.clear();
187207
}
188208

209+
private void runSafelyOnUiThread(final Runnable runnable, final String tag) {
210+
try {
211+
if (MainThreadChecker.isMainThread()) {
212+
runnable.run();
213+
} else {
214+
handler.post(
215+
() -> {
216+
try {
217+
runnable.run();
218+
} catch (Throwable t) {
219+
if (logger != null) {
220+
logger.log(SentryLevel.ERROR, "Failed to execute " + tag, t);
221+
}
222+
}
223+
});
224+
}
225+
} catch (Throwable t) {
226+
if (logger != null) {
227+
logger.log(SentryLevel.ERROR, "Failed to execute " + tag, t);
228+
}
229+
}
230+
}
231+
189232
private static final class FrameCounts {
190233
private final int totalFrames;
191234
private final int slowFrames;

sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ class ActivityFramesTrackerTest {
2424
val activity = mock<Activity>()
2525
val sentryId = SentryId()
2626
val loadClass = mock<LoadClass>()
27+
val handler = mock<MainLooperHandler>()
2728

2829
fun getSut(): ActivityFramesTracker {
29-
return ActivityFramesTracker(aggregator)
30+
return ActivityFramesTracker(aggregator, handler)
3031
}
3132
}
3233
private val fixture = Fixture()
@@ -341,6 +342,42 @@ class ActivityFramesTrackerTest {
341342
assertNull(sut.takeMetrics(fixture.sentryId))
342343
}
343344

345+
@Test
346+
fun `addActivity call to FrameMetricsTracker is done on the main thread, even when being called from a background thread`() {
347+
val sut = fixture.getSut()
348+
349+
val addThread = Thread {
350+
sut.addActivity(fixture.activity)
351+
}
352+
addThread.start()
353+
addThread.join(500)
354+
verify(fixture.handler).post(any())
355+
}
356+
357+
@Test
358+
fun `setMetrics call to FrameMetricsTracker is done on the main thread, even when being called from a background thread`() {
359+
val sut = fixture.getSut()
360+
361+
val setMetricsThread = Thread {
362+
sut.setMetrics(fixture.activity, fixture.sentryId)
363+
}
364+
setMetricsThread.start()
365+
setMetricsThread.join(500)
366+
verify(fixture.handler).post(any())
367+
}
368+
369+
@Test
370+
fun `stop call to FrameMetricsTracker is done on the main thread, even when being called from a background thread`() {
371+
val sut = fixture.getSut()
372+
373+
val stopThread = Thread {
374+
sut.stop()
375+
}
376+
stopThread.start()
377+
stopThread.join(500)
378+
verify(fixture.handler).post(any())
379+
}
380+
344381
private fun getArray(frameTime: Int = 1, numFrames: Int = 1): Array<SparseIntArray?> {
345382
val totalArray = SparseIntArray()
346383
totalArray.put(frameTime, numFrames)

0 commit comments

Comments
 (0)