Skip to content

Commit 3d4d952

Browse files
committed
refactor(anr): Implement lazy file rotation for ANR profiling
1 parent 6e7fff2 commit 3d4d952

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,12 +507,21 @@ public class io/sentry/android/core/anr/AnrProfile {
507507

508508
public class io/sentry/android/core/anr/AnrProfileManager : java/io/Closeable {
509509
public fun <init> (Lio/sentry/SentryOptions;)V
510+
public fun <init> (Lio/sentry/SentryOptions;Ljava/io/File;)V
510511
public fun add (Lio/sentry/android/core/anr/AnrStackTrace;)V
511512
public fun clear ()V
512513
public fun close ()V
513514
public fun load ()Lio/sentry/android/core/anr/AnrProfile;
514515
}
515516

517+
public class io/sentry/android/core/anr/AnrProfileRotationHelper {
518+
public fun <init> ()V
519+
public static fun deleteLastFile (Ljava/io/File;)Z
520+
public static fun getCurrentFile (Ljava/io/File;)Ljava/io/File;
521+
public static fun getLastFile (Ljava/io/File;)Ljava/io/File;
522+
public static fun rotate ()V
523+
}
524+
516525
public class io/sentry/android/core/anr/AnrProfilingIntegration : io/sentry/Integration, io/sentry/android/core/AppState$AppStateListener, java/io/Closeable, java/lang/Runnable {
517526
public static final field POLLING_INTERVAL_MS J
518527
public static final field THRESHOLD_ANR_MS J

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.sentry.SendFireAndForgetOutboxSender;
2626
import io.sentry.SentryLevel;
2727
import io.sentry.SentryOpenTelemetryMode;
28+
import io.sentry.android.core.anr.AnrProfileRotationHelper;
2829
import io.sentry.android.core.anr.AnrProfilingIntegration;
2930
import io.sentry.android.core.cache.AndroidEnvelopeCache;
3031
import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader;
@@ -138,6 +139,8 @@ static void loadDefaultAndMetadataOptions(
138139
.getRuntimeManager()
139140
.runWithRelaxedPolicy(() -> getCacheDir(finalContext).getAbsolutePath()));
140141

142+
AnrProfileRotationHelper.rotate();
143+
141144
readDefaultOptionValues(options, finalContext, buildInfoProvider);
142145
AppState.getInstance().registerLifecycleObserver(options);
143146
}

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.sentry.android.core.anr.AnrException;
2525
import io.sentry.android.core.anr.AnrProfile;
2626
import io.sentry.android.core.anr.AnrProfileManager;
27+
import io.sentry.android.core.anr.AnrProfileRotationHelper;
2728
import io.sentry.android.core.anr.StackTraceConverter;
2829
import io.sentry.android.core.cache.AndroidEnvelopeCache;
2930
import io.sentry.android.core.internal.threaddump.Lines;
@@ -47,6 +48,7 @@
4748
import java.io.ByteArrayInputStream;
4849
import java.io.ByteArrayOutputStream;
4950
import java.io.Closeable;
51+
import java.io.File;
5052
import java.io.IOException;
5153
import java.io.InputStream;
5254
import java.io.InputStreamReader;
@@ -322,10 +324,25 @@ private void applyAnrProfile(
322324
}
323325

324326
@Nullable AnrProfile anrProfile = null;
325-
try (final AnrProfileManager provider = new AnrProfileManager(options)) {
326-
anrProfile = provider.load();
327+
final File cacheDir = new File(options.getCacheDirPath());
328+
329+
try {
330+
final File lastFile = AnrProfileRotationHelper.getLastFile(cacheDir);
331+
332+
if (lastFile.exists()) {
333+
options.getLogger().log(SentryLevel.DEBUG, "Reading ANR profile from rotated file");
334+
try (final AnrProfileManager provider = new AnrProfileManager(options, lastFile)) {
335+
anrProfile = provider.load();
336+
}
337+
} else {
338+
options.getLogger().log(SentryLevel.DEBUG, "No ANR profile file found");
339+
}
327340
} catch (Throwable t) {
328-
options.getLogger().log(SentryLevel.INFO, "Could not retrieve ANR profile");
341+
options.getLogger().log(SentryLevel.INFO, "Could not retrieve ANR profile", t);
342+
} finally {
343+
if (AnrProfileRotationHelper.deleteLastFile(cacheDir)) {
344+
options.getLogger().log(SentryLevel.DEBUG, "Deleted old ANR profile file");
345+
}
329346
}
330347

331348
if (anrProfile != null) {
@@ -342,8 +359,6 @@ private void applyAnrProfile(
342359
ProfileChunk.PLATFORM_JAVA,
343360
options);
344361
chunk.setSentryProfile(profile);
345-
346-
options.getLogger().log(SentryLevel.DEBUG, "");
347362
scopes.captureProfileChunk(chunk);
348363

349364
final @Nullable AggregatedStackTrace culprit =

sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ public class AnrProfileManager implements Closeable {
2828
@NotNull private final ObjectQueue<AnrStackTrace> queue;
2929

3030
public AnrProfileManager(final @NotNull SentryOptions options) {
31+
this(options, new File(options.getCacheDirPath(), "anr_profile"));
32+
}
3133

32-
final @NotNull File file = new File(options.getCacheDirPath(), "anr_profile");
34+
public AnrProfileManager(final @NotNull SentryOptions options, final @NotNull File file) {
3335
final @NotNull ILogger logger = options.getLogger();
3436

3537
@Nullable QueueFile queueFile = null;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.sentry.android.core.anr;
2+
3+
import java.io.File;
4+
import java.util.concurrent.atomic.AtomicBoolean;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
/**
9+
* Coordinates file rotation between AnrProfilingIntegration and AnrV2Integration to prevent
10+
* concurrent access to the same QueueFile.
11+
*/
12+
@ApiStatus.Internal
13+
public class AnrProfileRotationHelper {
14+
15+
private static final String CURRENT_FILE_NAME = "anr_profile";
16+
private static final String OLD_FILE_NAME = "anr_profile_old";
17+
18+
private static final AtomicBoolean shouldRotate = new AtomicBoolean(false);
19+
private static final Object rotationLock = new Object();
20+
21+
public static void rotate() {
22+
shouldRotate.set(true);
23+
}
24+
25+
private static void performRotationIfNeeded(final @NotNull File cacheDir) {
26+
if (!shouldRotate.get()) {
27+
return;
28+
}
29+
30+
synchronized (rotationLock) {
31+
if (!shouldRotate.get()) {
32+
return;
33+
}
34+
35+
final File currentFile = new File(cacheDir, CURRENT_FILE_NAME);
36+
final File oldFile = new File(cacheDir, OLD_FILE_NAME);
37+
38+
if (oldFile.exists()) {
39+
oldFile.delete();
40+
}
41+
42+
if (currentFile.exists()) {
43+
currentFile.renameTo(oldFile);
44+
}
45+
46+
shouldRotate.set(false);
47+
}
48+
}
49+
50+
@NotNull
51+
public static File getCurrentFile(final @NotNull File cacheDir) {
52+
performRotationIfNeeded(cacheDir);
53+
return new File(cacheDir, CURRENT_FILE_NAME);
54+
}
55+
56+
@NotNull
57+
public static File getLastFile(final @NotNull File cacheDir) {
58+
performRotationIfNeeded(cacheDir);
59+
return new File(cacheDir, OLD_FILE_NAME);
60+
}
61+
62+
public static boolean deleteLastFile(final @NotNull File cacheDir) {
63+
final File oldFile = new File(cacheDir, OLD_FILE_NAME);
64+
return oldFile.exists() && oldFile.delete();
65+
}
66+
}

sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.util.AutoClosableReentrantLock;
1616
import io.sentry.util.Objects;
1717
import java.io.Closeable;
18+
import java.io.File;
1819
import java.io.IOException;
1920
import java.util.concurrent.atomic.AtomicBoolean;
2021
import org.jetbrains.annotations.ApiStatus;
@@ -178,16 +179,15 @@ protected MainThreadState getState() {
178179
@NonNull
179180
protected AnrProfileManager getProfileManager() {
180181
try (final @NotNull ISentryLifecycleToken ignored = profileManagerLock.acquire()) {
181-
final @Nullable AnrProfileManager r = profileManager;
182-
if (r != null) {
183-
return r;
184-
} else {
185-
186-
final AnrProfileManager newManager =
187-
new AnrProfileManager(Objects.requireNonNull(options, "Options can't be null"));
188-
profileManager = newManager;
189-
return newManager;
182+
if (profileManager == null) {
183+
final @NotNull SentryOptions opts =
184+
Objects.requireNonNull(options, "Options can't be null");
185+
final @NotNull File currentFile =
186+
AnrProfileRotationHelper.getCurrentFile(new File(opts.getCacheDirPath()));
187+
profileManager = new AnrProfileManager(opts, currentFile);
190188
}
189+
190+
return profileManager;
191191
}
192192
}
193193

0 commit comments

Comments
 (0)