Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Fixes

- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724))
- Use logger from options for JVM profiler ([#4771](https://github.com/getsentry/sentry-java/pull/4771))
- Session Replay: Avoid deadlock when pausing replay if no connection ([#4788](https://github.com/getsentry/sentry-java/pull/4788))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.sentry.android.core.internal.modules.AssetsModulesLoader;
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider;
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
import io.sentry.android.core.internal.util.AndroidRuntimeManager;
import io.sentry.android.core.internal.util.AndroidThreadChecker;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
Expand Down Expand Up @@ -108,7 +109,7 @@ static void loadDefaultAndMetadataOptions(
final @NotNull BuildInfoProvider buildInfoProvider) {
Objects.requireNonNull(context, "The context is required.");

context = ContextUtils.getApplicationContext(context);
@NotNull final Context finalContext = ContextUtils.getApplicationContext(context);

Objects.requireNonNull(options, "The options object is required.");
Objects.requireNonNull(logger, "The ILogger object is required.");
Expand All @@ -120,17 +121,22 @@ static void loadDefaultAndMetadataOptions(
options.setDefaultScopeType(ScopeType.CURRENT);
options.setOpenTelemetryMode(SentryOpenTelemetryMode.OFF);
options.setDateProvider(new SentryAndroidDateProvider());
options.setRuntimeManager(new AndroidRuntimeManager());

// set a lower flush timeout on Android to avoid ANRs
options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS);

options.setFrameMetricsCollector(
new SentryFrameMetricsCollector(context, logger, buildInfoProvider));
new SentryFrameMetricsCollector(finalContext, logger, buildInfoProvider));

ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());
ManifestMetadataReader.applyMetadata(finalContext, options, buildInfoProvider);

readDefaultOptionValues(options, context, buildInfoProvider);
options.setCacheDirPath(
options
.getRuntimeManager()
.runWithRelaxedPolicy(() -> getCacheDir(finalContext).getAbsolutePath()));

readDefaultOptionValues(options, finalContext, buildInfoProvider);
AppState.getInstance().registerLifecycleObserver(options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import io.sentry.SentryOptions;
import io.sentry.TracesSampler;
import io.sentry.TracesSamplingDecision;
import io.sentry.android.core.internal.util.AndroidRuntimeManager;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.runtime.IRuntimeManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
Expand Down Expand Up @@ -108,7 +110,9 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
return;
}

final @NotNull File cacheDir = AndroidOptionsInitializer.getCacheDir(context);
final @NotNull IRuntimeManager runtimeManager = new AndroidRuntimeManager();
final @NotNull File cacheDir =
runtimeManager.runWithRelaxedPolicy(() -> AndroidOptionsInitializer.getCacheDir(context));
final @NotNull File configFile = new File(cacheDir, APP_START_PROFILING_CONFIG_FILE_NAME);

// No config exists: app start profiling is not enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import org.jetbrains.annotations.ApiStatus;
Expand Down Expand Up @@ -157,18 +158,18 @@ public static boolean hasStartupCrashMarker(final @NotNull SentryOptions options

final File lastAnrMarker = new File(cacheDirPath, LAST_ANR_REPORT);
try {
if (lastAnrMarker.exists() && lastAnrMarker.canRead()) {
final String content = FileUtils.readText(lastAnrMarker);
// we wrapped into try-catch already
//noinspection ConstantConditions
return content.equals("null") ? null : Long.parseLong(content.trim());
} else {
final String content = FileUtils.readText(lastAnrMarker);
// we wrapped into try-catch already
//noinspection ConstantConditions
return content.equals("null") ? null : Long.parseLong(content.trim());
} catch (Throwable e) {
if (e instanceof FileNotFoundException) {
options
.getLogger()
.log(DEBUG, "Last ANR marker does not exist. %s.", lastAnrMarker.getAbsolutePath());
} else {
options.getLogger().log(ERROR, "Error reading last ANR marker", e);
}
} catch (Throwable e) {
options.getLogger().log(ERROR, "Error reading last ANR marker", e);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.sentry.android.core.internal.util;

import android.os.StrictMode;
import io.sentry.util.runtime.IRuntimeManager;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public final class AndroidRuntimeManager implements IRuntimeManager {
@Override
public <T> T runWithRelaxedPolicy(final @NotNull IRuntimeManagerCallback<T> toRun) {
final @NotNull StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
final @NotNull StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy();
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
try {
return toRun.run();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
StrictMode.setVmPolicy(oldVmPolicy);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ private CpuInfoUtils() {}
if (!cpuDir.getName().matches("cpu[0-9]+")) continue;
File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH);

if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue;

long khz;
try {
String content = FileUtils.readText(cpuMaxFreqFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator
import io.sentry.android.core.internal.modules.AssetsModulesLoader
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider
import io.sentry.android.core.internal.util.AndroidRuntimeManager
import io.sentry.android.core.internal.util.AndroidThreadChecker
import io.sentry.android.core.performance.AppStartMetrics
import io.sentry.android.fragment.FragmentLifecycleIntegration
Expand Down Expand Up @@ -917,4 +918,10 @@ class AndroidOptionsInitializerTest {
fixture.sentryOptions.compositePerformanceCollector is DefaultCompositePerformanceCollector
}
}

@Test
fun `AndroidRuntimeManager is set in the options`() {
fixture.initSut()
assertIs<AndroidRuntimeManager>(fixture.sentryOptions.runtimeManager)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.sentry.android.core.internal.util

import android.os.StrictMode
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AndroidRuntimeManagerTest {

val sut = AndroidRuntimeManager()

@AfterTest
fun `clean up`() {
// Revert StrictMode policies to avoid issues with other tests
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX)
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX)
}

@Test
fun `runWithRelaxedPolicy changes policy when running and restores it afterwards`() {
var called = false
val threadPolicy = StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build()
val vmPolicy = StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()
assertNotEquals(StrictMode.ThreadPolicy.LAX, threadPolicy)
assertNotEquals(StrictMode.VmPolicy.LAX, vmPolicy)

// Set and assert the StrictMode policies
StrictMode.setThreadPolicy(threadPolicy)
StrictMode.setVmPolicy(vmPolicy)
assertEquals(threadPolicy.toString(), StrictMode.getThreadPolicy().toString())
assertEquals(vmPolicy.toString(), StrictMode.getVmPolicy().toString())

// Run the function and assert LAX policies
called =
sut.runWithRelaxedPolicy {
assertEquals(
StrictMode.ThreadPolicy.LAX.toString(),
StrictMode.getThreadPolicy().toString(),
)
assertEquals(StrictMode.VmPolicy.LAX.toString(), StrictMode.getVmPolicy().toString())
true
}

// Policies should be reverted back
assertEquals(threadPolicy.toString(), StrictMode.getThreadPolicy().toString())
assertEquals(vmPolicy.toString(), StrictMode.getVmPolicy().toString())

// Ensure the code ran
assertTrue(called)
}

@Test
fun `runWithRelaxedPolicy changes policy and restores it afterwards even if the code throws`() {
var called = false
val threadPolicy = StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build()
val vmPolicy = StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()

// Set and assert the StrictMode policies
StrictMode.setThreadPolicy(threadPolicy)
StrictMode.setVmPolicy(vmPolicy)

// Run the function and assert LAX policies
try {
sut.runWithRelaxedPolicy {
assertEquals(
StrictMode.ThreadPolicy.LAX.toString(),
StrictMode.getThreadPolicy().toString(),
)
assertEquals(StrictMode.VmPolicy.LAX.toString(), StrictMode.getVmPolicy().toString())
called = true
throw Exception("Test exception")
}
} catch (_: Exception) {}

// Policies should be reverted back
assertEquals(threadPolicy.toString(), StrictMode.getThreadPolicy().toString())
assertEquals(vmPolicy.toString(), StrictMode.getVmPolicy().toString())

// Ensure the code ran
assertTrue(called)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.uitest.android

import android.os.StrictMode
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand Down Expand Up @@ -252,6 +253,15 @@ class SdkInitTests : BaseUiTest() {
assertDefaultIntegrations()
}

@Test
fun initNotThrowStrictMode() {
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyDeath().build())
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build())
val sampleScenario = launchActivity<EmptyActivity>()
initSentry()
sampleScenario.moveToState(Lifecycle.State.DESTROYED)
}

private fun assertDefaultIntegrations() {
val integrations =
mutableListOf(
Expand Down
15 changes: 15 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3394,6 +3394,7 @@ public class io/sentry/SentryOptions {
public fun getReadTimeoutMillis ()I
public fun getRelease ()Ljava/lang/String;
public fun getReplayController ()Lio/sentry/ReplayController;
public fun getRuntimeManager ()Lio/sentry/util/runtime/IRuntimeManager;
public fun getSampleRate ()Ljava/lang/Double;
public fun getScopeObservers ()Ljava/util/List;
public fun getSdkVersion ()Lio/sentry/protocol/SdkVersion;
Expand Down Expand Up @@ -3539,6 +3540,7 @@ public class io/sentry/SentryOptions {
public fun setReadTimeoutMillis (I)V
public fun setRelease (Ljava/lang/String;)V
public fun setReplayController (Lio/sentry/ReplayController;)V
public fun setRuntimeManager (Lio/sentry/util/runtime/IRuntimeManager;)V
public fun setSampleRate (Ljava/lang/Double;)V
public fun setSdkVersion (Lio/sentry/protocol/SdkVersion;)V
public fun setSendClientReports (Z)V
Expand Down Expand Up @@ -7293,6 +7295,19 @@ public final class io/sentry/util/UrlUtils$UrlDetails {
public fun getUrlOrFallback ()Ljava/lang/String;
}

public abstract interface class io/sentry/util/runtime/IRuntimeManager {
public abstract fun runWithRelaxedPolicy (Lio/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback;)Ljava/lang/Object;
}

public abstract interface class io/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback {
public abstract fun run ()Ljava/lang/Object;
}

public final class io/sentry/util/runtime/NeutralRuntimeManager : io/sentry/util/runtime/IRuntimeManager {
public fun <init> ()V
public fun runWithRelaxedPolicy (Lio/sentry/util/runtime/IRuntimeManager$IRuntimeManagerCallback;)Ljava/lang/Object;
}

public abstract interface class io/sentry/util/thread/IThreadChecker {
public abstract fun currentThreadSystemId ()J
public abstract fun getCurrentThreadName ()Ljava/lang/String;
Expand Down
24 changes: 6 additions & 18 deletions sentry/src/main/java/io/sentry/DirectoryProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,22 @@ public void processDirectory(final @NotNull File directory) {
try {
logger.log(SentryLevel.DEBUG, "Processing dir. %s", directory.getAbsolutePath());

if (!directory.exists()) {
final File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name));
if (filteredListFiles == null) {
logger.log(
SentryLevel.WARNING,
"Directory '%s' doesn't exist. No cached events to send.",
SentryLevel.ERROR,
"Cache dir %s is null or is not a directory.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so does listFiles have the isDirectory check under the hood?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. From the javadoc
If this abstract pathname does not denote a directory, then this method returns null

directory.getAbsolutePath());
return;
}
if (!directory.isDirectory()) {
logger.log(
SentryLevel.ERROR, "Cache dir %s is not a directory.", directory.getAbsolutePath());
return;
}

final File[] listFiles = directory.listFiles();
if (listFiles == null) {
logger.log(SentryLevel.ERROR, "Cache dir %s is null.", directory.getAbsolutePath());
return;
}

final File[] filteredListFiles = directory.listFiles((d, name) -> isRelevantFileName(name));

logger.log(
SentryLevel.DEBUG,
"Processing %d items from cache dir %s",
filteredListFiles != null ? filteredListFiles.length : 0,
filteredListFiles.length,
directory.getAbsolutePath());

for (File file : listFiles) {
for (File file : filteredListFiles) {
// it ignores .sentry-native database folder and new ones that might come up
if (!file.isFile()) {
logger.log(SentryLevel.DEBUG, "File %s is not a File.", file.getAbsolutePath());
Expand Down
9 changes: 5 additions & 4 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,8 @@ private static boolean preInitConfigurations(final @NotNull SentryOptions option
return true;
}

@SuppressWarnings("FutureReturnValueIgnored")
// older AGP versions do not support method references
@SuppressWarnings({"FutureReturnValueIgnored", "Convert2MethodRef"})
private static void initConfigurations(final @NotNull SentryOptions options) {
final @NotNull ILogger logger = options.getLogger();
logger.log(SentryLevel.INFO, "Initializing SDK with DSN: '%s'", options.getDsn());
Expand All @@ -592,15 +593,15 @@ private static void initConfigurations(final @NotNull SentryOptions options) {
final String outboxPath = options.getOutboxPath();
if (outboxPath != null) {
final File outboxDir = new File(outboxPath);
outboxDir.mkdirs();
options.getRuntimeManager().runWithRelaxedPolicy(() -> outboxDir.mkdirs());
} else {
logger.log(SentryLevel.INFO, "No outbox dir path is defined in options.");
}

final String cacheDirPath = options.getCacheDirPath();
if (cacheDirPath != null) {
final File cacheDir = new File(cacheDirPath);
cacheDir.mkdirs();
options.getRuntimeManager().runWithRelaxedPolicy(() -> cacheDir.mkdirs());
final IEnvelopeCache envelopeCache = options.getEnvelopeDiskCache();
// only overwrite the cache impl if it's not already set
if (envelopeCache instanceof NoOpEnvelopeCache) {
Expand All @@ -613,7 +614,7 @@ private static void initConfigurations(final @NotNull SentryOptions options) {
&& profilingTracesDirPath != null) {

final File profilingTracesDir = new File(profilingTracesDirPath);
profilingTracesDir.mkdirs();
options.getRuntimeManager().runWithRelaxedPolicy(() -> profilingTracesDir.mkdirs());

try {
options
Expand Down
Loading
Loading