From acb5ad64c6b17b63447af04ea2e564551d653821 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 25 Sep 2024 05:23:05 +0200 Subject: [PATCH 1/2] replace synchronized with lock --- .../api/sentry-android-core.api | 4 + .../core/ActivityBreadcrumbsIntegration.java | 45 ++- .../android/core/ActivityFramesTracker.java | 123 +++---- .../core/ActivityLifecycleIntegration.java | 173 +++++----- .../core/AndroidOptionsInitializer.java | 3 +- .../sentry/android/core/AndroidProfiler.java | 311 +++++++++--------- .../core/AndroidTransactionProfiler.java | 244 +++++++------- .../sentry/android/core/AnrIntegration.java | 15 +- .../java/io/sentry/android/core/AppState.java | 9 +- .../sentry/android/core/DeviceInfoUtil.java | 7 +- .../core/EnvelopeFileObserverIntegration.java | 8 +- .../io/sentry/android/core/Installation.java | 29 +- .../sentry/android/core/LifecycleWatcher.java | 8 +- .../PerformanceAndroidEventProcessor.java | 114 +++---- .../PhoneStateBreadcrumbsIntegration.java | 8 +- .../core/SendCachedEnvelopeIntegration.java | 7 +- .../io/sentry/android/core/SentryAndroid.java | 10 +- .../core/SentryPerformanceProvider.java | 23 +- .../core/SpanFrameMetricsCollector.java | 14 +- .../SystemEventsBreadcrumbsIntegration.java | 8 +- .../TempSensorBreadcrumbsIntegration.java | 8 +- .../core/internal/util/CpuInfoUtils.java | 51 +-- .../core/performance/AppStartMetrics.java | 7 +- .../sentry/android/ndk/DebugImagesLoader.java | 9 +- .../io/sentry/android/replay/ReplayCache.kt | 43 +-- .../replay/capture/BaseCaptureStrategy.kt | 2 +- .../android/replay/capture/CaptureStrategy.kt | 5 +- .../gestures/ComposeGestureTargetLocator.java | 5 +- .../ComposeViewHierarchyExporter.java | 5 +- .../SentryGraphqlExceptionHandler.java | 7 +- sentry-jdbc/api/sentry-jdbc.api | 1 + .../sentry/jdbc/SentryJdbcEventListener.java | 7 +- .../sentry/opentelemetry/OtelSpanWrapper.java | 4 +- .../opentelemetry/SentryWeakSpanStorage.java | 6 +- .../api/sentry-spring-jakarta.api | 1 + .../spring/jakarta/SentryRequestResolver.java | 6 +- sentry-spring/api/sentry-spring.api | 1 + .../sentry/spring/SentryRequestResolver.java | 6 +- sentry/api/sentry.api | 8 + ...efaultTransactionPerformanceCollector.java | 7 +- sentry/src/main/java/io/sentry/Hint.java | 57 ++-- .../java/io/sentry/MainEventProcessor.java | 5 +- .../java/io/sentry/MetricsAggregator.java | 11 +- sentry/src/main/java/io/sentry/Scope.java | 23 +- ...achedEnvelopeFireAndForgetIntegration.java | 6 +- sentry/src/main/java/io/sentry/Sentry.java | 137 ++++---- .../io/sentry/SentryCrashLastRunState.java | 10 +- .../java/io/sentry/SentryExecutorService.java | 6 +- .../SentryIntegrationPackageStorage.java | 5 +- .../main/java/io/sentry/SentryOptions.java | 5 +- .../java/io/sentry/SentrySpanStorage.java | 5 +- .../src/main/java/io/sentry/SentryTracer.java | 16 +- sentry/src/main/java/io/sentry/Session.java | 7 +- sentry/src/main/java/io/sentry/Stack.java | 5 +- .../io/sentry/SynchronizedCollection.java | 38 ++- .../java/io/sentry/SynchronizedQueue.java | 22 +- .../java/io/sentry/cache/EnvelopeCache.java | 23 +- .../metrics/LocalMetricsAggregator.java | 7 +- .../java/io/sentry/protocol/Contexts.java | 8 +- .../util/AutoClosableReentrantLock.java | 29 ++ .../java/io/sentry/util/LazyEvaluator.java | 12 +- .../util/AutoClosableReentrantLockTest.kt | 17 + 62 files changed, 1046 insertions(+), 760 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/AutoClosableReentrantLock.java create mode 100644 sentry/src/test/java/io/sentry/util/AutoClosableReentrantLockTest.kt diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index f1d5f8e7d7..6b1a6b5738 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -63,6 +63,7 @@ public class io/sentry/android/core/AndroidMemoryCollector : io/sentry/IPerforma } public class io/sentry/android/core/AndroidProfiler { + protected final field lock Lio/sentry/util/AutoClosableReentrantLock; public fun (Ljava/lang/String;ILio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ISentryExecutorService;Lio/sentry/ILogger;Lio/sentry/android/core/BuildInfoProvider;)V public fun close ()V public fun endAndCollect (ZLjava/util/List;)Lio/sentry/android/core/AndroidProfiler$ProfileEndData; @@ -194,6 +195,7 @@ public final class io/sentry/android/core/DeviceInfoUtil { } public abstract class io/sentry/android/core/EnvelopeFileObserverIntegration : io/sentry/Integration, java/io/Closeable { + protected final field startLock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun close ()V public static fun getOutboxFileObserver ()Lio/sentry/android/core/EnvelopeFileObserverIntegration; @@ -355,6 +357,7 @@ public final class io/sentry/android/core/SentryPerformanceProvider { } public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerformanceContinuousCollector, io/sentry/android/core/internal/util/SentryFrameMetricsCollector$FrameMetricsCollectorListener { + protected final field lock Lio/sentry/util/AutoClosableReentrantLock; public fun (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V public fun clear ()V public fun onFrameMetricCollected (JJJJZZF)V @@ -431,6 +434,7 @@ public class io/sentry/android/core/performance/ActivityLifecycleTimeSpan : java } public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter { + public static final field staticLock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V public fun clear ()V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java index 4a5ca63717..6832090ef5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java @@ -9,9 +9,11 @@ import io.sentry.Breadcrumb; import io.sentry.Hint; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; @@ -25,6 +27,7 @@ public final class ActivityBreadcrumbsIntegration private final @NotNull Application application; private @Nullable IScopes scopes; private boolean enabled; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public ActivityBreadcrumbsIntegration(final @NotNull Application application) { this.application = Objects.requireNonNull(application, "Application is required"); @@ -64,40 +67,54 @@ public void close() throws IOException { } @Override - public synchronized void onActivityCreated( + public void onActivityCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - addBreadcrumb(activity, "created"); + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "created"); + } } @Override - public synchronized void onActivityStarted(final @NotNull Activity activity) { - addBreadcrumb(activity, "started"); + public void onActivityStarted(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "started"); + } } @Override - public synchronized void onActivityResumed(final @NotNull Activity activity) { - addBreadcrumb(activity, "resumed"); + public void onActivityResumed(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "resumed"); + } } @Override - public synchronized void onActivityPaused(final @NotNull Activity activity) { - addBreadcrumb(activity, "paused"); + public void onActivityPaused(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "paused"); + } } @Override - public synchronized void onActivityStopped(final @NotNull Activity activity) { - addBreadcrumb(activity, "stopped"); + public void onActivityStopped(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "stopped"); + } } @Override - public synchronized void onActivitySaveInstanceState( + public void onActivitySaveInstanceState( final @NotNull Activity activity, final @NotNull Bundle outState) { - addBreadcrumb(activity, "saveInstanceState"); + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "saveInstanceState"); + } } @Override - public synchronized void onActivityDestroyed(final @NotNull Activity activity) { - addBreadcrumb(activity, "destroyed"); + public void onActivityDestroyed(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + addBreadcrumb(activity, "destroyed"); + } } private void addBreadcrumb(final @NotNull Activity activity, final @NotNull String state) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java index 99d230b305..7475c64cba 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java @@ -3,11 +3,13 @@ import android.app.Activity; import android.util.SparseIntArray; import androidx.core.app.FrameMetricsAggregator; +import io.sentry.ISentryLifecycleToken; import io.sentry.MeasurementUnit; import io.sentry.SentryLevel; import io.sentry.android.core.internal.util.AndroidMainThreadChecker; import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; +import io.sentry.util.AutoClosableReentrantLock; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; @@ -37,6 +39,7 @@ public final class ActivityFramesTracker { new WeakHashMap<>(); private final @NotNull MainLooperHandler handler; + protected @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public ActivityFramesTracker( final @NotNull io.sentry.util.LoadClass loadClass, @@ -78,13 +81,15 @@ public boolean isFrameMetricsAggregatorAvailable() { } @SuppressWarnings("NullAway") - public synchronized void addActivity(final @NotNull Activity activity) { - if (!isFrameMetricsAggregatorAvailable()) { - return; - } + public void addActivity(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isFrameMetricsAggregatorAvailable()) { + return; + } - runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add"); - snapshotFrameCountsAtStart(activity); + runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add"); + snapshotFrameCountsAtStart(activity); + } } private void snapshotFrameCountsAtStart(final @NotNull Activity activity) { @@ -132,45 +137,46 @@ private void snapshotFrameCountsAtStart(final @NotNull Activity activity) { } @SuppressWarnings("NullAway") - public synchronized void setMetrics( - final @NotNull Activity activity, final @NotNull SentryId transactionId) { - if (!isFrameMetricsAggregatorAvailable()) { - return; - } + public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId transactionId) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isFrameMetricsAggregatorAvailable()) { + return; + } - // NOTE: removing an activity does not reset the frame counts, only reset() does - // throws IllegalArgumentException when attempting to remove - // OnFrameMetricsAvailableListener - // that was never added. - // there's no contains method. - // throws NullPointerException when attempting to remove - // OnFrameMetricsAvailableListener and - // there was no - // Observers, See - // https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b - runSafelyOnUiThread(() -> frameMetricsAggregator.remove(activity), null); - - final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity); - - if (frameCounts == null - || (frameCounts.totalFrames == 0 - && frameCounts.slowFrames == 0 - && frameCounts.frozenFrames == 0)) { - return; - } + // NOTE: removing an activity does not reset the frame counts, only reset() does + // throws IllegalArgumentException when attempting to remove + // OnFrameMetricsAvailableListener + // that was never added. + // there's no contains method. + // throws NullPointerException when attempting to remove + // OnFrameMetricsAvailableListener and + // there was no + // Observers, See + // https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b + runSafelyOnUiThread(() -> frameMetricsAggregator.remove(activity), null); + + final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity); + + if (frameCounts == null + || (frameCounts.totalFrames == 0 + && frameCounts.slowFrames == 0 + && frameCounts.frozenFrames == 0)) { + return; + } - final MeasurementValue tfValues = - new MeasurementValue(frameCounts.totalFrames, MeasurementUnit.NONE); - final MeasurementValue sfValues = - new MeasurementValue(frameCounts.slowFrames, MeasurementUnit.NONE); - final MeasurementValue ffValues = - new MeasurementValue(frameCounts.frozenFrames, MeasurementUnit.NONE); - final Map measurements = new HashMap<>(); - measurements.put(MeasurementValue.KEY_FRAMES_TOTAL, tfValues); - measurements.put(MeasurementValue.KEY_FRAMES_SLOW, sfValues); - measurements.put(MeasurementValue.KEY_FRAMES_FROZEN, ffValues); - - activityMeasurements.put(transactionId, measurements); + final MeasurementValue tfValues = + new MeasurementValue(frameCounts.totalFrames, MeasurementUnit.NONE); + final MeasurementValue sfValues = + new MeasurementValue(frameCounts.slowFrames, MeasurementUnit.NONE); + final MeasurementValue ffValues = + new MeasurementValue(frameCounts.frozenFrames, MeasurementUnit.NONE); + final Map measurements = new HashMap<>(); + measurements.put(MeasurementValue.KEY_FRAMES_TOTAL, tfValues); + measurements.put(MeasurementValue.KEY_FRAMES_SLOW, sfValues); + measurements.put(MeasurementValue.KEY_FRAMES_FROZEN, ffValues); + + activityMeasurements.put(transactionId, measurements); + } } private @Nullable FrameCounts diffFrameCountsAtEnd(final @NotNull Activity activity) { @@ -192,25 +198,28 @@ public synchronized void setMetrics( } @Nullable - public synchronized Map takeMetrics( - final @NotNull SentryId transactionId) { - if (!isFrameMetricsAggregatorAvailable()) { - return null; - } + public Map takeMetrics(final @NotNull SentryId transactionId) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isFrameMetricsAggregatorAvailable()) { + return null; + } - final Map stringMeasurementValueMap = - activityMeasurements.get(transactionId); - activityMeasurements.remove(transactionId); - return stringMeasurementValueMap; + final Map stringMeasurementValueMap = + activityMeasurements.get(transactionId); + activityMeasurements.remove(transactionId); + return stringMeasurementValueMap; + } } @SuppressWarnings("NullAway") - public synchronized void stop() { - if (isFrameMetricsAggregatorAvailable()) { - runSafelyOnUiThread(() -> frameMetricsAggregator.stop(), "FrameMetricsAggregator.stop"); - frameMetricsAggregator.reset(); + public void stop() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (isFrameMetricsAggregatorAvailable()) { + runSafelyOnUiThread(() -> frameMetricsAggregator.stop(), "FrameMetricsAggregator.stop"); + frameMetricsAggregator.reset(); + } + activityMeasurements.clear(); } - activityMeasurements.clear(); } private void runSafelyOnUiThread(final Runnable runnable, final String tag) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 3d72963238..37e00fb105 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -14,6 +14,7 @@ import io.sentry.FullyDisplayedReporter; import io.sentry.IScope; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.ISpan; import io.sentry.ITransaction; import io.sentry.Instrumenter; @@ -34,6 +35,7 @@ import io.sentry.android.core.performance.TimeSpan; import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.TransactionNameSource; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; import java.io.Closeable; @@ -88,6 +90,7 @@ public final class ActivityLifecycleIntegration new WeakHashMap<>(); private final @NotNull ActivityFramesTracker activityFramesTracker; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public ActivityLifecycleIntegration( final @NotNull Application application, @@ -378,50 +381,56 @@ private void finishTransaction( } @Override - public synchronized void onActivityCreated( + public void onActivityCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { - setColdStart(savedInstanceState); - if (scopes != null && options != null && options.isEnableScreenTracking()) { - final @Nullable String activityClassName = ClassUtil.getClassName(activity); - scopes.configureScope(scope -> scope.setScreen(activityClassName)); - } - startTracing(activity); - final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + setColdStart(savedInstanceState); + if (scopes != null && options != null && options.isEnableScreenTracking()) { + final @Nullable String activityClassName = ClassUtil.getClassName(activity); + scopes.configureScope(scope -> scope.setScreen(activityClassName)); + } + startTracing(activity); + final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); - firstActivityCreated = true; + firstActivityCreated = true; - if (fullyDisplayedReporter != null) { - fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan)); + if (fullyDisplayedReporter != null) { + fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan)); + } } } @Override - public synchronized void onActivityStarted(final @NotNull Activity activity) { - if (performanceEnabled) { - // The docs on the screen rendering performance tracing - // (https://firebase.google.com/docs/perf-mon/screen-traces?platform=android#definition), - // state that the tracing starts for every Activity class when the app calls - // .onActivityStarted. - // Adding an Activity in onActivityCreated leads to Window.FEATURE_NO_TITLE not - // working. Moving this to onActivityStarted fixes the problem. - activityFramesTracker.addActivity(activity); + public void onActivityStarted(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (performanceEnabled) { + // The docs on the screen rendering performance tracing + // (https://firebase.google.com/docs/perf-mon/screen-traces?platform=android#definition), + // state that the tracing starts for every Activity class when the app calls + // .onActivityStarted. + // Adding an Activity in onActivityCreated leads to Window.FEATURE_NO_TITLE not + // working. Moving this to onActivityStarted fixes the problem. + activityFramesTracker.addActivity(activity); + } } } @Override - public synchronized void onActivityResumed(final @NotNull Activity activity) { - if (performanceEnabled) { - - final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity); - final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); - final View rootView = activity.findViewById(android.R.id.content); - if (rootView != null) { - FirstDrawDoneListener.registerForNextDraw( - rootView, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider); - } else { - // Posting a task to the main thread's handler will make it executed after it finished - // its current job. That is, right after the activity draws the layout. - mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan)); + public void onActivityResumed(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (performanceEnabled) { + + final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity); + final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); + final View rootView = activity.findViewById(android.R.id.content); + if (rootView != null) { + FirstDrawDoneListener.registerForNextDraw( + rootView, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider); + } else { + // Posting a task to the main thread's handler will make it executed after it finished + // its current job. That is, right after the activity draws the layout. + mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan)); + } } } } @@ -448,64 +457,74 @@ public void onActivityPrePaused(@NonNull Activity activity) { } @Override - public synchronized void onActivityPaused(final @NotNull Activity activity) { - // only executed if API < 29 otherwise it happens on onActivityPrePaused - if (!isAllActivityCallbacksAvailable) { - // as the SDK may gets (re-)initialized mid activity lifecycle, ensure we set the flag here as - // well - // this ensures any newly launched activity will not use the app start timestamp as txn start - firstActivityCreated = true; - if (scopes == null) { - lastPausedTime = AndroidDateUtils.getCurrentSentryDateTime(); - } else { - lastPausedTime = scopes.getOptions().getDateProvider().now(); + public void onActivityPaused(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // only executed if API < 29 otherwise it happens on onActivityPrePaused + if (!isAllActivityCallbacksAvailable) { + // as the SDK may gets (re-)initialized mid activity lifecycle, ensure we set the flag here + // as + // well + // this ensures any newly launched activity will not use the app start timestamp as txn + // start + firstActivityCreated = true; + if (scopes == null) { + lastPausedTime = AndroidDateUtils.getCurrentSentryDateTime(); + } else { + lastPausedTime = scopes.getOptions().getDateProvider().now(); + } } } } @Override - public synchronized void onActivityStopped(final @NotNull Activity activity) { - // no-op + public void onActivityStopped(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // no-op + } } @Override - public synchronized void onActivitySaveInstanceState( + public void onActivitySaveInstanceState( final @NotNull Activity activity, final @NotNull Bundle outState) { - // no-op + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // no-op + } } @Override - public synchronized void onActivityDestroyed(final @NotNull Activity activity) { - if (performanceEnabled) { - - // in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid - // memory leak - finishSpan(appStartSpan, SpanStatus.CANCELLED); - - // we finish the ttidSpan as cancelled in case it isn't completed yet - final ISpan ttidSpan = ttidSpanMap.get(activity); - final ISpan ttfdSpan = ttfdSpanMap.get(activity); - finishSpan(ttidSpan, SpanStatus.DEADLINE_EXCEEDED); - - // we finish the ttfdSpan as deadline_exceeded in case it isn't completed yet - finishExceededTtfdSpan(ttfdSpan, ttidSpan); - cancelTtfdAutoClose(); - - // in case people opt-out enableActivityLifecycleTracingAutoFinish and forgot to finish it, - // we make sure to finish it when the activity gets destroyed. - stopTracing(activity, true); + public void onActivityDestroyed(final @NotNull Activity activity) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (performanceEnabled) { + + // in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid + // memory leak + finishSpan(appStartSpan, SpanStatus.CANCELLED); + + // we finish the ttidSpan as cancelled in case it isn't completed yet + final ISpan ttidSpan = ttidSpanMap.get(activity); + final ISpan ttfdSpan = ttfdSpanMap.get(activity); + finishSpan(ttidSpan, SpanStatus.DEADLINE_EXCEEDED); + + // we finish the ttfdSpan as deadline_exceeded in case it isn't completed yet + finishExceededTtfdSpan(ttfdSpan, ttidSpan); + cancelTtfdAutoClose(); + + // in case people opt-out enableActivityLifecycleTracingAutoFinish and forgot to finish it, + // we make sure to finish it when the activity gets destroyed. + stopTracing(activity, true); + + // set it to null in case its been just finished as cancelled + appStartSpan = null; + ttidSpanMap.remove(activity); + ttfdSpanMap.remove(activity); + } - // set it to null in case its been just finished as cancelled - appStartSpan = null; - ttidSpanMap.remove(activity); - ttfdSpanMap.remove(activity); + // clear it up, so we don't start again for the same activity if the activity is in the + // activity + // stack still. + // if the activity is opened again and not in memory, transactions will be created normally. + activitiesWithOngoingTransactions.remove(activity); } - - // clear it up, so we don't start again for the same activity if the activity is in the - // activity - // stack still. - // if the activity is opened again and not in memory, transactions will be created normally. - activitiesWithOngoingTransactions.remove(activity); } private void finishSpan(final @Nullable ISpan span) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index b5b0708164..90662df648 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -8,6 +8,7 @@ import io.sentry.DeduplicateMultithreadedEventProcessor; import io.sentry.DefaultTransactionPerformanceCollector; import io.sentry.ILogger; +import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; import io.sentry.NoOpConnectionStatusProvider; import io.sentry.ScopeType; @@ -161,7 +162,7 @@ static void initializeIntegrationsAndProcessors( // Check if the profiler was already instantiated in the app start. // We use the Android profiler, that uses a global start/stop api, so we need to preserve the // state of the profiler, and it's only possible retaining the instance. - synchronized (AppStartMetrics.getInstance()) { + try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { final @Nullable ITransactionProfiler appStartProfiler = AppStartMetrics.getInstance().getAppStartProfiler(); if (appStartProfiler != null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java index d24025c551..379123c2c8 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java @@ -9,12 +9,14 @@ import io.sentry.DateUtils; import io.sentry.ILogger; import io.sentry.ISentryExecutorService; +import io.sentry.ISentryLifecycleToken; import io.sentry.MemoryCollectionData; import io.sentry.PerformanceCollectionData; import io.sentry.SentryLevel; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.profilemeasurements.ProfileMeasurement; import io.sentry.profilemeasurements.ProfileMeasurementValue; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.File; import java.util.ArrayDeque; @@ -96,6 +98,7 @@ public ProfileEndData( private final @NotNull ISentryExecutorService executorService; private final @NotNull ILogger logger; private boolean isRunning = false; + protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public AndroidProfiler( final @NotNull String tracesFilesDirPath, @@ -116,176 +119,184 @@ public AndroidProfiler( } @SuppressLint("NewApi") - public synchronized @Nullable ProfileStartData start() { - // intervalUs is 0 only if there was a problem in the init - if (intervalUs == 0) { - logger.log( - SentryLevel.WARNING, "Disabling profiling because intervaUs is set to %d", intervalUs); - return null; - } + public @Nullable ProfileStartData start() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // intervalUs is 0 only if there was a problem in the init + if (intervalUs == 0) { + logger.log( + SentryLevel.WARNING, "Disabling profiling because intervaUs is set to %d", intervalUs); + return null; + } - if (isRunning) { - logger.log(SentryLevel.WARNING, "Profiling has already started..."); - return null; - } + if (isRunning) { + logger.log(SentryLevel.WARNING, "Profiling has already started..."); + return null; + } - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) return null; - - // We create a file with a uuid name, so no need to check if it already exists - traceFile = new File(traceFilesDir, UUID.randomUUID() + ".trace"); - - measurementsMap.clear(); - screenFrameRateMeasurements.clear(); - slowFrameRenderMeasurements.clear(); - frozenFrameRenderMeasurements.clear(); - - frameMetricsCollectorId = - frameMetricsCollector.startCollection( - new SentryFrameMetricsCollector.FrameMetricsCollectorListener() { - float lastRefreshRate = 0; - - @Override - public void onFrameMetricCollected( - final long frameStartNanos, - final long frameEndNanos, - final long durationNanos, - final long delayNanos, - final boolean isSlow, - final boolean isFrozen, - final float refreshRate) { - // profileStartNanos is calculated through SystemClock.elapsedRealtimeNanos(), - // but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp - // relative to profileStartNanos - final long frameTimestampRelativeNanos = - frameEndNanos - - System.nanoTime() - + SystemClock.elapsedRealtimeNanos() - - profileStartNanos; - - // We don't allow negative relative timestamps. - // So we add a check, even if this should never happen. - if (frameTimestampRelativeNanos < 0) { - return; - } - if (isFrozen) { - frozenFrameRenderMeasurements.addLast( - new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos)); - } else if (isSlow) { - slowFrameRenderMeasurements.addLast( - new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos)); + // and SystemClock.elapsedRealtimeNanos() since Jelly Bean + if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) return null; + + // We create a file with a uuid name, so no need to check if it already exists + traceFile = new File(traceFilesDir, UUID.randomUUID() + ".trace"); + + measurementsMap.clear(); + screenFrameRateMeasurements.clear(); + slowFrameRenderMeasurements.clear(); + frozenFrameRenderMeasurements.clear(); + + frameMetricsCollectorId = + frameMetricsCollector.startCollection( + new SentryFrameMetricsCollector.FrameMetricsCollectorListener() { + float lastRefreshRate = 0; + + @Override + public void onFrameMetricCollected( + final long frameStartNanos, + final long frameEndNanos, + final long durationNanos, + final long delayNanos, + final boolean isSlow, + final boolean isFrozen, + final float refreshRate) { + // profileStartNanos is calculated through SystemClock.elapsedRealtimeNanos(), + // but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp + // relative to profileStartNanos + final long frameTimestampRelativeNanos = + frameEndNanos + - System.nanoTime() + + SystemClock.elapsedRealtimeNanos() + - profileStartNanos; + + // We don't allow negative relative timestamps. + // So we add a check, even if this should never happen. + if (frameTimestampRelativeNanos < 0) { + return; + } + if (isFrozen) { + frozenFrameRenderMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos)); + } else if (isSlow) { + slowFrameRenderMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, durationNanos)); + } + if (refreshRate != lastRefreshRate) { + lastRefreshRate = refreshRate; + screenFrameRateMeasurements.addLast( + new ProfileMeasurementValue(frameTimestampRelativeNanos, refreshRate)); + } } - if (refreshRate != lastRefreshRate) { - lastRefreshRate = refreshRate; - screenFrameRateMeasurements.addLast( - new ProfileMeasurementValue(frameTimestampRelativeNanos, refreshRate)); - } - } - }); - - // We stop profiling after a timeout to avoid huge profiles to be sent - try { - scheduledFinish = - executorService.schedule(() -> endAndCollect(true, null), PROFILING_TIMEOUT_MILLIS); - } catch (RejectedExecutionException e) { - logger.log( - SentryLevel.ERROR, - "Failed to call the executor. Profiling will not be automatically finished. Did you call Sentry.close()?", - e); - } + }); + + // We stop profiling after a timeout to avoid huge profiles to be sent + try { + scheduledFinish = + executorService.schedule(() -> endAndCollect(true, null), PROFILING_TIMEOUT_MILLIS); + } catch (RejectedExecutionException e) { + logger.log( + SentryLevel.ERROR, + "Failed to call the executor. Profiling will not be automatically finished. Did you call Sentry.close()?", + e); + } - profileStartNanos = SystemClock.elapsedRealtimeNanos(); - final @NotNull Date profileStartTimestamp = DateUtils.getCurrentDateTime(); - long profileStartCpuMillis = Process.getElapsedCpuTime(); - - // We don't make any check on the file existence or writeable state, because we don't want to - // make file IO in the main thread. - // We cannot offload the work to the executorService, as if that's very busy, profiles could - // start/stop with a lot of delay and even cause ANRs. - try { - // If there is any problem with the file this method will throw (but it will not throw in - // tests) - Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs); - isRunning = true; - return new ProfileStartData(profileStartNanos, profileStartCpuMillis, profileStartTimestamp); - } catch (Throwable e) { - endAndCollect(false, null); - logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e); - isRunning = false; - return null; + profileStartNanos = SystemClock.elapsedRealtimeNanos(); + final @NotNull Date profileStartTimestamp = DateUtils.getCurrentDateTime(); + long profileStartCpuMillis = Process.getElapsedCpuTime(); + + // We don't make any check on the file existence or writeable state, because we don't want to + // make file IO in the main thread. + // We cannot offload the work to the executorService, as if that's very busy, profiles could + // start/stop with a lot of delay and even cause ANRs. + try { + // If there is any problem with the file this method will throw (but it will not throw in + // tests) + Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs); + isRunning = true; + return new ProfileStartData( + profileStartNanos, profileStartCpuMillis, profileStartTimestamp); + } catch (Throwable e) { + endAndCollect(false, null); + logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e); + isRunning = false; + return null; + } } } @SuppressLint("NewApi") - public synchronized @Nullable ProfileEndData endAndCollect( + public @Nullable ProfileEndData endAndCollect( final boolean isTimeout, final @Nullable List performanceCollectionData) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!isRunning) { + logger.log(SentryLevel.WARNING, "Profiler not running"); + return null; + } - if (!isRunning) { - logger.log(SentryLevel.WARNING, "Profiler not running"); - return null; - } + // and SystemClock.elapsedRealtimeNanos() since Jelly Bean + if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) return null; + + try { + // If there is any problem with the file this method could throw, but the start is also + // wrapped, so this should never happen (except for tests, where this is the only method + // that + // throws) + Debug.stopMethodTracing(); + } catch (Throwable e) { + logger.log(SentryLevel.ERROR, "Error while stopping profiling: ", e); + } finally { + isRunning = false; + } + frameMetricsCollector.stopCollection(frameMetricsCollectorId); - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) return null; - - try { - // If there is any problem with the file this method could throw, but the start is also - // wrapped, so this should never happen (except for tests, where this is the only method that - // throws) - Debug.stopMethodTracing(); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error while stopping profiling: ", e); - } finally { - isRunning = false; - } - frameMetricsCollector.stopCollection(frameMetricsCollectorId); + long transactionEndNanos = SystemClock.elapsedRealtimeNanos(); + long transactionEndCpuMillis = Process.getElapsedCpuTime(); - long transactionEndNanos = SystemClock.elapsedRealtimeNanos(); - long transactionEndCpuMillis = Process.getElapsedCpuTime(); + if (traceFile == null) { + logger.log(SentryLevel.ERROR, "Trace file does not exists"); + return null; + } - if (traceFile == null) { - logger.log(SentryLevel.ERROR, "Trace file does not exists"); - return null; - } + if (!slowFrameRenderMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_SLOW_FRAME_RENDERS, + new ProfileMeasurement( + ProfileMeasurement.UNIT_NANOSECONDS, slowFrameRenderMeasurements)); + } + if (!frozenFrameRenderMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_FROZEN_FRAME_RENDERS, + new ProfileMeasurement( + ProfileMeasurement.UNIT_NANOSECONDS, frozenFrameRenderMeasurements)); + } + if (!screenFrameRateMeasurements.isEmpty()) { + measurementsMap.put( + ProfileMeasurement.ID_SCREEN_FRAME_RATES, + new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements)); + } + putPerformanceCollectionDataInMeasurements(performanceCollectionData); - if (!slowFrameRenderMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_SLOW_FRAME_RENDERS, - new ProfileMeasurement(ProfileMeasurement.UNIT_NANOSECONDS, slowFrameRenderMeasurements)); - } - if (!frozenFrameRenderMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_FROZEN_FRAME_RENDERS, - new ProfileMeasurement( - ProfileMeasurement.UNIT_NANOSECONDS, frozenFrameRenderMeasurements)); - } - if (!screenFrameRateMeasurements.isEmpty()) { - measurementsMap.put( - ProfileMeasurement.ID_SCREEN_FRAME_RATES, - new ProfileMeasurement(ProfileMeasurement.UNIT_HZ, screenFrameRateMeasurements)); - } - putPerformanceCollectionDataInMeasurements(performanceCollectionData); + if (scheduledFinish != null) { + scheduledFinish.cancel(true); + scheduledFinish = null; + } - if (scheduledFinish != null) { - scheduledFinish.cancel(true); - scheduledFinish = null; + return new ProfileEndData( + transactionEndNanos, transactionEndCpuMillis, isTimeout, traceFile, measurementsMap); } - - return new ProfileEndData( - transactionEndNanos, transactionEndCpuMillis, isTimeout, traceFile, measurementsMap); } - public synchronized void close() { - // we cancel any scheduled work - if (scheduledFinish != null) { - scheduledFinish.cancel(true); - scheduledFinish = null; - } + public void close() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // we cancel any scheduled work + if (scheduledFinish != null) { + scheduledFinish.cancel(true); + scheduledFinish = null; + } - // stop profiling if running - if (isRunning) { - endAndCollect(true, null); + // stop profiling if running + if (isRunning) { + endAndCollect(true, null); + } } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java index eca5d744f6..d1aa3def99 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java @@ -13,6 +13,7 @@ import io.sentry.ILogger; import io.sentry.IScopes; import io.sentry.ISentryExecutorService; +import io.sentry.ISentryLifecycleToken; import io.sentry.ITransaction; import io.sentry.ITransactionProfiler; import io.sentry.PerformanceCollectionData; @@ -23,6 +24,7 @@ import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.CpuInfoUtils; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.ArrayList; import java.util.Date; @@ -47,6 +49,7 @@ final class AndroidTransactionProfiler implements ITransactionProfiler { private long profileStartNanos; private long profileStartCpuMillis; private @NotNull Date profileStartTimestamp; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); /** * @deprecated please use a constructor that doesn't takes a {@link IScopes} instead, as it would @@ -136,22 +139,24 @@ private void init() { } @Override - public synchronized void start() { - // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler - // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; - - // Let's initialize trace folder and profiling interval - init(); - - transactionsCounter++; - // When the first transaction is starting, we can start profiling - if (transactionsCounter == 1 && onFirstStart()) { - logger.log(SentryLevel.DEBUG, "Profiler started."); - } else { - transactionsCounter--; - logger.log( - SentryLevel.WARNING, "A profile is already running. This profile will be ignored."); + public void start() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler + // causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392 + if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return; + + // Let's initialize trace folder and profiling interval + init(); + + transactionsCounter++; + // When the first transaction is starting, we can start profiling + if (transactionsCounter == 1 && onFirstStart()) { + logger.log(SentryLevel.DEBUG, "Profiler started."); + } else { + transactionsCounter--; + logger.log( + SentryLevel.WARNING, "A profile is already running. This profile will be ignored."); + } } } @@ -174,133 +179,138 @@ private boolean onFirstStart() { } @Override - public synchronized void bindTransaction(final @NotNull ITransaction transaction) { - // If the profiler is running, but no profilingTransactionData is set, we bind it here - if (transactionsCounter > 0 && currentProfilingTransactionData == null) { - currentProfilingTransactionData = - new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis); + public void bindTransaction(final @NotNull ITransaction transaction) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // If the profiler is running, but no profilingTransactionData is set, we bind it here + if (transactionsCounter > 0 && currentProfilingTransactionData == null) { + currentProfilingTransactionData = + new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis); + } } } @Override - public @Nullable synchronized ProfilingTraceData onTransactionFinish( + public @Nullable ProfilingTraceData onTransactionFinish( final @NotNull ITransaction transaction, final @Nullable List performanceCollectionData, final @NotNull SentryOptions options) { - - return onTransactionFinish( - transaction.getName(), - transaction.getEventId().toString(), - transaction.getSpanContext().getTraceId().toString(), - false, - performanceCollectionData, - options); + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + return onTransactionFinish( + transaction.getName(), + transaction.getEventId().toString(), + transaction.getSpanContext().getTraceId().toString(), + false, + performanceCollectionData, + options); + } } @SuppressLint("NewApi") - private @Nullable synchronized ProfilingTraceData onTransactionFinish( + private @Nullable ProfilingTraceData onTransactionFinish( final @NotNull String transactionName, final @NotNull String transactionId, final @NotNull String traceId, final boolean isTimeout, final @Nullable List performanceCollectionData, final @NotNull SentryOptions options) { - // check if profiler was created - if (profiler == null) { - return null; - } - - // onTransactionStart() is only available since Lollipop_MR1 - // and SystemClock.elapsedRealtimeNanos() since Jelly Bean - if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null; + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + // check if profiler was created + if (profiler == null) { + return null; + } - // Transaction finished, but it's not in the current profile - if (currentProfilingTransactionData == null - || !currentProfilingTransactionData.getId().equals(transactionId)) { - // A transaction is finishing, but it's not profiled. We can skip it - logger.log( - SentryLevel.INFO, - "Transaction %s (%s) finished, but was not currently being profiled. Skipping", - transactionName, - traceId); - return null; - } + // onTransactionStart() is only available since Lollipop_MR1 + // and SystemClock.elapsedRealtimeNanos() since Jelly Bean + if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null; + + // Transaction finished, but it's not in the current profile + if (currentProfilingTransactionData == null + || !currentProfilingTransactionData.getId().equals(transactionId)) { + // A transaction is finishing, but it's not profiled. We can skip it + logger.log( + SentryLevel.INFO, + "Transaction %s (%s) finished, but was not currently being profiled. Skipping", + transactionName, + traceId); + return null; + } - if (transactionsCounter > 0) { - transactionsCounter--; - } + if (transactionsCounter > 0) { + transactionsCounter--; + } - logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId); + logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId); + + if (transactionsCounter != 0) { + // We notify the data referring to this transaction that it finished + if (currentProfilingTransactionData != null) { + currentProfilingTransactionData.notifyFinish( + SystemClock.elapsedRealtimeNanos(), + profileStartNanos, + Process.getElapsedCpuTime(), + profileStartCpuMillis); + } + return null; + } - if (transactionsCounter != 0) { - // We notify the data referring to this transaction that it finished - if (currentProfilingTransactionData != null) { - currentProfilingTransactionData.notifyFinish( - SystemClock.elapsedRealtimeNanos(), - profileStartNanos, - Process.getElapsedCpuTime(), - profileStartCpuMillis); + final AndroidProfiler.ProfileEndData endData = + profiler.endAndCollect(false, performanceCollectionData); + // check if profiler end successfully + if (endData == null) { + return null; } - return null; - } - final AndroidProfiler.ProfileEndData endData = - profiler.endAndCollect(false, performanceCollectionData); - // check if profiler end successfully - if (endData == null) { - return null; - } + long transactionDurationNanos = endData.endNanos - profileStartNanos; - long transactionDurationNanos = endData.endNanos - profileStartNanos; + List transactionList = new ArrayList<>(1); + final ProfilingTransactionData txData = currentProfilingTransactionData; + if (txData != null) { + transactionList.add(txData); + } + currentProfilingTransactionData = null; + // We clear the counter in case of a timeout + transactionsCounter = 0; + + String totalMem = "0"; + ActivityManager.MemoryInfo memInfo = getMemInfo(); + if (memInfo != null) { + totalMem = Long.toString(memInfo.totalMem); + } + String[] abis = Build.SUPPORTED_ABIS; - List transactionList = new ArrayList<>(1); - final ProfilingTransactionData txData = currentProfilingTransactionData; - if (txData != null) { - transactionList.add(txData); - } - currentProfilingTransactionData = null; - // We clear the counter in case of a timeout - transactionsCounter = 0; - - String totalMem = "0"; - ActivityManager.MemoryInfo memInfo = getMemInfo(); - if (memInfo != null) { - totalMem = Long.toString(memInfo.totalMem); - } - String[] abis = Build.SUPPORTED_ABIS; + // We notify all transactions data that all transactions finished. + // Some may not have been really finished, in case of a timeout + for (ProfilingTransactionData t : transactionList) { + t.notifyFinish( + endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis); + } - // We notify all transactions data that all transactions finished. - // Some may not have been really finished, in case of a timeout - for (ProfilingTransactionData t : transactionList) { - t.notifyFinish( - endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis); + // cpu max frequencies are read with a lambda because reading files is involved, so it will be + // done in the background when the trace file is read + return new ProfilingTraceData( + endData.traceFile, + profileStartTimestamp, + transactionList, + transactionName, + transactionId, + traceId, + Long.toString(transactionDurationNanos), + buildInfoProvider.getSdkInfoVersion(), + abis != null && abis.length > 0 ? abis[0] : "", + () -> CpuInfoUtils.getInstance().readMaxFrequencies(), + buildInfoProvider.getManufacturer(), + buildInfoProvider.getModel(), + buildInfoProvider.getVersionRelease(), + buildInfoProvider.isEmulator(), + totalMem, + options.getProguardUuid(), + options.getRelease(), + options.getEnvironment(), + (endData.didTimeout || isTimeout) + ? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT + : ProfilingTraceData.TRUNCATION_REASON_NORMAL, + endData.measurementsMap); } - - // cpu max frequencies are read with a lambda because reading files is involved, so it will be - // done in the background when the trace file is read - return new ProfilingTraceData( - endData.traceFile, - profileStartTimestamp, - transactionList, - transactionName, - transactionId, - traceId, - Long.toString(transactionDurationNanos), - buildInfoProvider.getSdkInfoVersion(), - abis != null && abis.length > 0 ? abis[0] : "", - () -> CpuInfoUtils.getInstance().readMaxFrequencies(), - buildInfoProvider.getManufacturer(), - buildInfoProvider.getModel(), - buildInfoProvider.getVersionRelease(), - buildInfoProvider.isEmulator(), - totalMem, - options.getProguardUuid(), - options.getRelease(), - options.getEnvironment(), - (endData.didTimeout || isTimeout) - ? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT - : ProfilingTraceData.TRUNCATION_REASON_NORMAL, - endData.measurementsMap); } @Override diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java index 90d53a3c9b..9ee6fcd23a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrIntegration.java @@ -6,6 +6,7 @@ import android.content.Context; import io.sentry.Hint; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SentryEvent; import io.sentry.SentryLevel; @@ -14,6 +15,7 @@ import io.sentry.hints.AbnormalExit; import io.sentry.hints.TransactionEnd; import io.sentry.protocol.Mechanism; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HintUtils; import io.sentry.util.Objects; import java.io.Closeable; @@ -30,7 +32,7 @@ public final class AnrIntegration implements Integration, Closeable { private final @NotNull Context context; private boolean isClosed = false; - private final @NotNull Object startLock = new Object(); + private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); public AnrIntegration(final @NotNull Context context) { this.context = context; @@ -45,7 +47,8 @@ public AnrIntegration(final @NotNull Context context) { private @Nullable SentryOptions options; - private static final @NotNull Object watchDogLock = new Object(); + protected static final @NotNull AutoClosableReentrantLock watchDogLock = + new AutoClosableReentrantLock(); @Override public final void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) { @@ -66,7 +69,7 @@ private void register( .getExecutorService() .submit( () -> { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { if (!isClosed) { startAnrWatchdog(scopes, options); } @@ -82,7 +85,7 @@ private void register( private void startAnrWatchdog( final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - synchronized (watchDogLock) { + try (final @NotNull ISentryLifecycleToken ignored = watchDogLock.acquire()) { if (anrWatchDog == null) { options .getLogger() @@ -151,10 +154,10 @@ ANRWatchDog getANRWatchDog() { @Override public void close() throws IOException { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { isClosed = true; } - synchronized (watchDogLock) { + try (final @NotNull ISentryLifecycleToken ignored = watchDogLock.acquire()) { if (anrWatchDog != null) { anrWatchDog.interrupt(); anrWatchDog = null; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java index d47372c0c8..d9633aed54 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppState.java @@ -1,5 +1,7 @@ package io.sentry.android.core; +import io.sentry.ISentryLifecycleToken; +import io.sentry.util.AutoClosableReentrantLock; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -9,6 +11,7 @@ @ApiStatus.Internal public final class AppState { private static @NotNull AppState instance = new AppState(); + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private AppState() {} @@ -27,7 +30,9 @@ void resetInstance() { return inBackground; } - synchronized void setInBackground(final boolean inBackground) { - this.inBackground = inBackground; + void setInBackground(final boolean inBackground) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + this.inBackground = inBackground; + } } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java index f1debc5d23..e24faec7a0 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java @@ -15,6 +15,7 @@ import android.os.SystemClock; import android.util.DisplayMetrics; import io.sentry.DateUtils; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.CpuInfoUtils; @@ -22,6 +23,7 @@ import io.sentry.android.core.internal.util.RootChecker; import io.sentry.protocol.Device; import io.sentry.protocol.OperatingSystem; +import io.sentry.util.AutoClosableReentrantLock; import java.io.File; import java.util.Calendar; import java.util.Collections; @@ -40,6 +42,9 @@ public final class DeviceInfoUtil { @SuppressLint("StaticFieldLeak") private static volatile DeviceInfoUtil instance; + private static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); + private final @NotNull Context context; private final @NotNull SentryAndroidOptions options; private final @NotNull BuildInfoProvider buildInfoProvider; @@ -74,7 +79,7 @@ public DeviceInfoUtil( public static DeviceInfoUtil getInstance( final @NotNull Context context, final @NotNull SentryAndroidOptions options) { if (instance == null) { - synchronized (DeviceInfoUtil.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (instance == null) { instance = new DeviceInfoUtil(context.getApplicationContext(), options); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java index 6e821e5be7..a921f79458 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/EnvelopeFileObserverIntegration.java @@ -2,10 +2,12 @@ import io.sentry.ILogger; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.OutboxSender; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.Closeable; import org.jetbrains.annotations.NotNull; @@ -17,7 +19,7 @@ public abstract class EnvelopeFileObserverIntegration implements Integration, Cl private @Nullable EnvelopeFileObserver observer; private @Nullable ILogger logger; private boolean isClosed = false; - private final @NotNull Object startLock = new Object(); + protected final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); public static @NotNull EnvelopeFileObserverIntegration getOutboxFileObserver() { return new OutboxEnvelopeFileObserverIntegration(); @@ -44,7 +46,7 @@ public final void register(final @NotNull IScopes scopes, final @NotNull SentryO .getExecutorService() .submit( () -> { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { if (!isClosed) { startOutboxSender(scopes, options, path); } @@ -88,7 +90,7 @@ private void startOutboxSender( @Override public void close() { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { isClosed = true; } if (observer != null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java index 007bb306cd..4c9b5ddbc2 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/Installation.java @@ -1,6 +1,8 @@ package io.sentry.android.core; import android.content.Context; +import io.sentry.ISentryLifecycleToken; +import io.sentry.util.AutoClosableReentrantLock; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -19,6 +21,9 @@ final class Installation { private static final Charset UTF_8 = Charset.forName("UTF-8"); + protected static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); + private Installation() {} /** @@ -29,20 +34,22 @@ private Installation() {} * @return the generated installationId * @throws RuntimeException if not possible to read nor to write to the file. */ - public static synchronized String id(final @NotNull Context context) throws RuntimeException { - if (deviceId == null) { - final File installation = new File(context.getFilesDir(), INSTALLATION); - try { - if (!installation.exists()) { - deviceId = writeInstallationFile(installation); - return deviceId; + public static String id(final @NotNull Context context) throws RuntimeException { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { + if (deviceId == null) { + final File installation = new File(context.getFilesDir(), INSTALLATION); + try { + if (!installation.exists()) { + deviceId = writeInstallationFile(installation); + return deviceId; + } + deviceId = readInstallationFile(installation); + } catch (Throwable e) { + throw new RuntimeException(e); } - deviceId = readInstallationFile(installation); - } catch (Throwable e) { - throw new RuntimeException(e); } + return deviceId; } - return deviceId; } @TestOnly diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java index 399e560a5b..286f59f7c0 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/LifecycleWatcher.java @@ -4,11 +4,13 @@ import androidx.lifecycle.LifecycleOwner; import io.sentry.Breadcrumb; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; import io.sentry.Session; import io.sentry.android.core.internal.util.BreadcrumbFactory; import io.sentry.transport.CurrentDateProvider; import io.sentry.transport.ICurrentDateProvider; +import io.sentry.util.AutoClosableReentrantLock; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; @@ -26,7 +28,7 @@ final class LifecycleWatcher implements DefaultLifecycleObserver { private @Nullable TimerTask timerTask; private final @NotNull Timer timer = new Timer(true); - private final @NotNull Object timerLock = new Object(); + private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock(); private final @NotNull IScopes scopes; private final boolean enableSessionTracking; private final boolean enableAppLifecycleBreadcrumbs; @@ -117,7 +119,7 @@ public void onStop(final @NotNull LifecycleOwner owner) { } private void scheduleEndSession() { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { cancelTask(); if (timer != null) { timerTask = @@ -138,7 +140,7 @@ public void run() { } private void cancelTask() { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timerTask != null) { timerTask.cancel(); timerTask = null; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java index afefed676f..edbcbeb905 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java @@ -7,6 +7,7 @@ import android.os.Looper; import io.sentry.EventProcessor; import io.sentry.Hint; +import io.sentry.ISentryLifecycleToken; import io.sentry.MeasurementUnit; import io.sentry.SentryEvent; import io.sentry.SpanContext; @@ -21,6 +22,7 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.SentrySpan; import io.sentry.protocol.SentryTransaction; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.HashMap; import java.util.List; @@ -44,6 +46,7 @@ final class PerformanceAndroidEventProcessor implements EventProcessor { private final @NotNull ActivityFramesTracker activityFramesTracker; private final @NotNull SentryAndroidOptions options; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); PerformanceAndroidEventProcessor( final @NotNull SentryAndroidOptions options, @@ -71,70 +74,71 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { @SuppressWarnings("NullAway") @Override - public synchronized @NotNull SentryTransaction process( + public @NotNull SentryTransaction process( @NotNull SentryTransaction transaction, @NotNull Hint hint) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!options.isTracingEnabled()) { + return transaction; + } - if (!options.isTracingEnabled()) { - return transaction; - } + // the app start measurement is only sent once and only if the transaction has + // the app.start span, which is automatically created by the SDK. + if (hasAppStartSpan(transaction)) { + if (!sentStartMeasurement) { + final @NotNull TimeSpan appStartTimeSpan = + AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); + final long appStartUpDurationMs = appStartTimeSpan.getDurationMs(); + + // if appStartUpDurationMs is 0, metrics are not ready to be sent + if (appStartUpDurationMs != 0) { + final MeasurementValue value = + new MeasurementValue( + (float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName()); + + final String appStartKey = + AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD + ? MeasurementValue.KEY_APP_START_COLD + : MeasurementValue.KEY_APP_START_WARM; + + transaction.getMeasurements().put(appStartKey, value); + + attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction); + sentStartMeasurement = true; + } + } - // the app start measurement is only sent once and only if the transaction has - // the app.start span, which is automatically created by the SDK. - if (hasAppStartSpan(transaction)) { - if (!sentStartMeasurement) { - final @NotNull TimeSpan appStartTimeSpan = - AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options); - final long appStartUpDurationMs = appStartTimeSpan.getDurationMs(); - - // if appStartUpDurationMs is 0, metrics are not ready to be sent - if (appStartUpDurationMs != 0) { - final MeasurementValue value = - new MeasurementValue( - (float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName()); - - final String appStartKey = - AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD - ? MeasurementValue.KEY_APP_START_COLD - : MeasurementValue.KEY_APP_START_WARM; - - transaction.getMeasurements().put(appStartKey, value); - - attachColdAppStartSpans(AppStartMetrics.getInstance(), transaction); - sentStartMeasurement = true; + @Nullable App appContext = transaction.getContexts().getApp(); + if (appContext == null) { + appContext = new App(); + transaction.getContexts().setApp(appContext); } + final String appStartType = + AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD + ? "cold" + : "warm"; + appContext.setStartType(appStartType); } - @Nullable App appContext = transaction.getContexts().getApp(); - if (appContext == null) { - appContext = new App(); - transaction.getContexts().setApp(appContext); + setContributingFlags(transaction); + + final SentryId eventId = transaction.getEventId(); + final SpanContext spanContext = transaction.getContexts().getTrace(); + + // only add slow/frozen frames to transactions created by ActivityLifecycleIntegration + // which have the operation UI_LOAD_OP. If a user-defined (or hybrid SDK) transaction + // users it, we'll also add the metrics if available + if (eventId != null + && spanContext != null + && spanContext.getOperation().contentEquals(UI_LOAD_OP)) { + final Map framesMetrics = + activityFramesTracker.takeMetrics(eventId); + if (framesMetrics != null) { + transaction.getMeasurements().putAll(framesMetrics); + } } - final String appStartType = - AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD - ? "cold" - : "warm"; - appContext.setStartType(appStartType); - } - setContributingFlags(transaction); - - final SentryId eventId = transaction.getEventId(); - final SpanContext spanContext = transaction.getContexts().getTrace(); - - // only add slow/frozen frames to transactions created by ActivityLifecycleIntegration - // which have the operation UI_LOAD_OP. If a user-defined (or hybrid SDK) transaction - // users it, we'll also add the metrics if available - if (eventId != null - && spanContext != null - && spanContext.getOperation().contentEquals(UI_LOAD_OP)) { - final Map framesMetrics = - activityFramesTracker.takeMetrics(eventId); - if (framesMetrics != null) { - transaction.getMeasurements().putAll(framesMetrics); - } + return transaction; } - - return transaction; } private void setContributingFlags(SentryTransaction transaction) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java index cae1492d3e..33ffd0be0a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PhoneStateBreadcrumbsIntegration.java @@ -7,10 +7,12 @@ import android.telephony.TelephonyManager; import io.sentry.Breadcrumb; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.Permissions; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; @@ -25,7 +27,7 @@ public final class PhoneStateBreadcrumbsIntegration implements Integration, Clos @TestOnly @Nullable PhoneStateChangeListener listener; private @Nullable TelephonyManager telephonyManager; private boolean isClosed = false; - private final @NotNull Object startLock = new Object(); + private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); public PhoneStateBreadcrumbsIntegration(final @NotNull Context context) { this.context = Objects.requireNonNull(context, "Context is required"); @@ -53,7 +55,7 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions .getExecutorService() .submit( () -> { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { if (!isClosed) { startTelephonyListener(scopes, options); } @@ -94,7 +96,7 @@ private void startTelephonyListener( @SuppressWarnings("deprecation") @Override public void close() throws IOException { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { isClosed = true; } if (telephonyManager != null && listener != null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java index 64f1cab362..9b50f3b6e5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java @@ -3,11 +3,13 @@ import io.sentry.DataCategory; import io.sentry.IConnectionStatusProvider; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SendCachedEnvelopeFireAndForgetIntegration; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.transport.RateLimiter; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.LazyEvaluator; import io.sentry.util.Objects; import java.io.Closeable; @@ -33,6 +35,7 @@ final class SendCachedEnvelopeIntegration private @Nullable SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget sender; private final AtomicBoolean isInitialized = new AtomicBoolean(false); private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public SendCachedEnvelopeIntegration( final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory, @@ -75,9 +78,9 @@ public void onConnectionStatusChanged( } @SuppressWarnings({"NullAway"}) - private synchronized void sendCachedEnvelopes( + private void sendCachedEnvelopes( final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) { - try { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { final Future future = options .getExecutorService() diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java index 9f2092669d..d3c222e204 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java @@ -7,6 +7,7 @@ import android.os.SystemClock; import io.sentry.ILogger; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.OptionsContainer; import io.sentry.Sentry; @@ -18,6 +19,7 @@ import io.sentry.android.core.performance.TimeSpan; import io.sentry.android.fragment.FragmentLifecycleIntegration; import io.sentry.android.timber.SentryTimberIntegration; +import io.sentry.util.AutoClosableReentrantLock; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; @@ -44,6 +46,9 @@ public final class SentryAndroid { private static final String FRAGMENT_CLASS_NAME = "androidx.fragment.app.FragmentManager$FragmentLifecycleCallbacks"; + protected static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); + private SentryAndroid() {} /** @@ -85,12 +90,11 @@ public static void init( * @param configuration Sentry.OptionsConfiguration configuration handler */ @SuppressLint("NewApi") - public static synchronized void init( + public static void init( @NotNull final Context context, @NotNull ILogger logger, @NotNull Sentry.OptionsConfiguration configuration) { - - try { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { Sentry.init( OptionsContainer.create(SentryAndroidOptions.class), options -> { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index 2ad465f1e3..f647b7e747 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -14,6 +14,7 @@ import android.os.SystemClock; import androidx.annotation.NonNull; import io.sentry.ILogger; +import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; import io.sentry.JsonSerializer; import io.sentry.NoOpLogger; @@ -28,6 +29,7 @@ import io.sentry.android.core.performance.ActivityLifecycleTimeSpan; import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.android.core.performance.TimeSpan; +import io.sentry.util.AutoClosableReentrantLock; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -53,6 +55,7 @@ public final class SentryPerformanceProvider extends EmptySecureContentProvider private final @NotNull ILogger logger; private final @NotNull BuildInfoProvider buildInfoProvider; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); @TestOnly SentryPerformanceProvider( @@ -92,7 +95,7 @@ public String getType(@NotNull Uri uri) { @Override public void shutdown() { - synchronized (AppStartMetrics.getInstance()) { + try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) { final @Nullable ITransactionProfiler appStartProfiler = AppStartMetrics.getInstance().getAppStartProfiler(); if (appStartProfiler != null) { @@ -302,14 +305,16 @@ public void onActivityDestroyed(@NonNull Activity activity) { } @TestOnly - synchronized void onAppStartDone() { - final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); - appStartMetrics.getSdkInitTimeSpan().stop(); - appStartMetrics.getAppStartTimeSpan().stop(); - - if (app != null) { - if (activityCallback != null) { - app.unregisterActivityLifecycleCallbacks(activityCallback); + void onAppStartDone() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance(); + appStartMetrics.getSdkInitTimeSpan().stop(); + appStartMetrics.getAppStartTimeSpan().stop(); + + if (app != null) { + if (activityCallback != null) { + app.unregisterActivityLifecycleCallbacks(activityCallback); + } } } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java index 5535bccb91..18427dd589 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java @@ -2,6 +2,7 @@ import io.sentry.DateUtils; import io.sentry.IPerformanceContinuousCollector; +import io.sentry.ISentryLifecycleToken; import io.sentry.ISpan; import io.sentry.ITransaction; import io.sentry.NoOpSpan; @@ -11,6 +12,7 @@ import io.sentry.SpanDataConvention; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.MeasurementValue; +import io.sentry.util.AutoClosableReentrantLock; import java.util.Date; import java.util.Iterator; import java.util.SortedSet; @@ -34,7 +36,7 @@ public class SpanFrameMetricsCollector private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(new Date(0), 0); private final boolean enabled; - private final @NotNull Object lock = new Object(); + protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private final @NotNull SentryFrameMetricsCollector frameMetricsCollector; private volatile @Nullable String listenerId; @@ -85,7 +87,7 @@ public void onSpanStarted(final @NotNull ISpan span) { return; } - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { runningSpans.add(span); if (listenerId == null) { @@ -109,7 +111,7 @@ public void onSpanFinished(final @NotNull ISpan span) { } // ignore span if onSpanStarted was never called for it - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (!runningSpans.contains(span)) { return; } @@ -117,7 +119,7 @@ public void onSpanFinished(final @NotNull ISpan span) { captureFrameMetrics(span); - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (runningSpans.isEmpty()) { clear(); } else { @@ -130,7 +132,7 @@ public void onSpanFinished(final @NotNull ISpan span) { private void captureFrameMetrics(@NotNull final ISpan span) { // TODO lock still required? - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { boolean removed = runningSpans.remove(span); if (!removed) { return; @@ -224,7 +226,7 @@ private void captureFrameMetrics(@NotNull final ISpan span) { @Override public void clear() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (listenerId != null) { frameMetricsCollector.stopCollection(listenerId); listenerId = null; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index e15ab1614a..57fe9c96d5 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -42,11 +42,13 @@ import io.sentry.Breadcrumb; import io.sentry.Hint; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; import io.sentry.android.core.internal.util.Debouncer; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import io.sentry.util.StringUtils; import java.io.Closeable; @@ -69,7 +71,7 @@ public final class SystemEventsBreadcrumbsIntegration implements Integration, Cl private final @NotNull List actions; private boolean isClosed = false; - private final @NotNull Object startLock = new Object(); + private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) { this(context, getDefaultActions()); @@ -103,7 +105,7 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions .getExecutorService() .submit( () -> { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { if (!isClosed) { startSystemEventsReceiver(scopes, (SentryAndroidOptions) options); } @@ -192,7 +194,7 @@ private void startSystemEventsReceiver( @Override public void close() throws IOException { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { isClosed = true; } if (receiver != null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java index 4d0e9c7e60..01d5781976 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java @@ -12,9 +12,11 @@ import io.sentry.Breadcrumb; import io.sentry.Hint; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.Integration; import io.sentry.SentryLevel; import io.sentry.SentryOptions; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.Closeable; import java.io.IOException; @@ -31,7 +33,7 @@ public final class TempSensorBreadcrumbsIntegration @TestOnly @Nullable SensorManager sensorManager; private boolean isClosed = false; - private final @NotNull Object startLock = new Object(); + private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock(); public TempSensorBreadcrumbsIntegration(final @NotNull Context context) { this.context = Objects.requireNonNull(context, "Context is required"); @@ -59,7 +61,7 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions .getExecutorService() .submit( () -> { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { if (!isClosed) { startSensorListener(options); } @@ -100,7 +102,7 @@ private void startSensorListener(final @NotNull SentryOptions options) { @Override public void close() throws IOException { - synchronized (startLock) { + try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) { isClosed = true; } if (sensorManager != null) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java index 8dcb994fbc..019db99fc7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java @@ -1,5 +1,7 @@ package io.sentry.android.core.internal.util; +import io.sentry.ISentryLifecycleToken; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.FileUtils; import java.io.File; import java.io.IOException; @@ -14,6 +16,7 @@ public final class CpuInfoUtils { private static final CpuInfoUtils instance = new CpuInfoUtils(); + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public static CpuInfoUtils getInstance() { return instance; @@ -34,34 +37,36 @@ private CpuInfoUtils() {} * * @return A list with the frequency of each core of the cpu in Mhz */ - public synchronized @NotNull List readMaxFrequencies() { - if (!cpuMaxFrequenciesMhz.isEmpty()) { - return cpuMaxFrequenciesMhz; - } - File[] cpuDirs = new File(getSystemCpuPath()).listFiles(); - if (cpuDirs == null) { - return new ArrayList<>(); - } + public @NotNull List readMaxFrequencies() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (!cpuMaxFrequenciesMhz.isEmpty()) { + return cpuMaxFrequenciesMhz; + } + File[] cpuDirs = new File(getSystemCpuPath()).listFiles(); + if (cpuDirs == null) { + return new ArrayList<>(); + } - for (File cpuDir : cpuDirs) { - if (!cpuDir.getName().matches("cpu[0-9]+")) continue; - File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH); + for (File cpuDir : cpuDirs) { + if (!cpuDir.getName().matches("cpu[0-9]+")) continue; + File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH); - if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue; + if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue; - long khz; - try { - String content = FileUtils.readText(cpuMaxFreqFile); - if (content == null) continue; - khz = Long.parseLong(content.trim()); - } catch (NumberFormatException e) { - continue; - } catch (IOException e) { - continue; + long khz; + try { + String content = FileUtils.readText(cpuMaxFreqFile); + if (content == null) continue; + khz = Long.parseLong(content.trim()); + } catch (NumberFormatException e) { + continue; + } catch (IOException e) { + continue; + } + cpuMaxFrequenciesMhz.add((int) (khz / 1000)); } - cpuMaxFrequenciesMhz.add((int) (khz / 1000)); + return cpuMaxFrequenciesMhz; } - return cpuMaxFrequenciesMhz; } @VisibleForTesting diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 461ee5eed6..996c6ab171 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -10,12 +10,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import io.sentry.ISentryLifecycleToken; import io.sentry.ITransactionProfiler; import io.sentry.SentryDate; import io.sentry.SentryNanotimeDate; import io.sentry.TracesSamplingDecision; import io.sentry.android.core.ContextUtils; import io.sentry.android.core.SentryAndroidOptions; +import io.sentry.util.AutoClosableReentrantLock; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -43,6 +45,8 @@ public enum AppStartType { private static long CLASS_LOADED_UPTIME_MS = SystemClock.uptimeMillis(); private static volatile @Nullable AppStartMetrics instance; + public static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); private @NotNull AppStartType appStartType = AppStartType.UNKNOWN; private boolean appLaunchedInForeground = false; @@ -59,9 +63,8 @@ public enum AppStartType { private boolean isCallbackRegistered = false; public static @NotNull AppStartMetrics getInstance() { - if (instance == null) { - synchronized (AppStartMetrics.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (instance == null) { instance = new AppStartMetrics(); } diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/DebugImagesLoader.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/DebugImagesLoader.java index 2e069dcc74..1257325091 100644 --- a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/DebugImagesLoader.java +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/DebugImagesLoader.java @@ -1,11 +1,13 @@ package io.sentry.android.ndk; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; import io.sentry.SentryOptions; import io.sentry.android.core.IDebugImagesLoader; import io.sentry.android.core.SentryAndroidOptions; import io.sentry.ndk.NativeModuleListLoader; import io.sentry.protocol.DebugImage; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.ArrayList; import java.util.List; @@ -26,7 +28,8 @@ public final class DebugImagesLoader implements IDebugImagesLoader { private static @Nullable List debugImages; /** we need to lock it because it could be called from different threads */ - private static final @NotNull Object debugImagesLock = new Object(); + protected static final @NotNull AutoClosableReentrantLock debugImagesLock = + new AutoClosableReentrantLock(); public DebugImagesLoader( final @NotNull SentryAndroidOptions options, @@ -43,7 +46,7 @@ public DebugImagesLoader( */ @Override public @Nullable List loadDebugImages() { - synchronized (debugImagesLock) { + try (final @NotNull ISentryLifecycleToken ignored = debugImagesLock.acquire()) { if (debugImages == null) { try { final io.sentry.ndk.DebugImage[] debugImagesArr = moduleListLoader.loadModuleList(); @@ -75,7 +78,7 @@ public DebugImagesLoader( /** Clears the caching of debug images on sentry-native and here. */ @Override public void clearDebugImages() { - synchronized (debugImagesLock) { + try (final @NotNull ISentryLifecycleToken ignored = debugImagesLock.acquire()) { try { moduleListLoader.clearModuleList(); diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt index 3db92ea5d8..dcbdd84360 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt @@ -15,6 +15,7 @@ import io.sentry.android.replay.video.MuxerConfig import io.sentry.android.replay.video.SimpleVideoEncoder import io.sentry.protocol.SentryId import io.sentry.rrweb.RRWebEvent +import io.sentry.util.AutoClosableReentrantLock import io.sentry.util.FileUtils import java.io.Closeable import java.io.File @@ -43,7 +44,8 @@ public class ReplayCache( ) : Closeable { private val isClosed = AtomicBoolean(false) - private val encoderLock = Any() + private val encoderLock = AutoClosableReentrantLock() + private val lock = AutoClosableReentrantLock() private var encoder: SimpleVideoEncoder? = null internal val replayCacheDir: File? by lazy { @@ -147,7 +149,7 @@ public class ReplayCache( } // TODO: reuse instance of encoder and just change file path to create a different muxer - encoder = synchronized(encoderLock) { + encoder = encoderLock.acquire().use { SimpleVideoEncoder( options, MuxerConfig( @@ -195,7 +197,7 @@ public class ReplayCache( } var videoDuration: Long - synchronized(encoderLock) { + encoderLock.acquire().use { encoder?.release() videoDuration = encoder?.duration ?: 0 encoder = null @@ -209,7 +211,7 @@ public class ReplayCache( private fun encode(frame: ReplayFrame): Boolean { return try { val bitmap = BitmapFactory.decodeFile(frame.screenshot.absolutePath) - synchronized(encoderLock) { + encoderLock.acquire().use { encoder?.encode(bitmap) } bitmap.recycle() @@ -251,7 +253,7 @@ public class ReplayCache( } override fun close() { - synchronized(encoderLock) { + encoderLock.acquire().use { encoder?.release() encoder = null } @@ -259,25 +261,26 @@ public class ReplayCache( } // TODO: it's awful, choose a better serialization format - @Synchronized fun persistSegmentValues(key: String, value: String?) { - if (isClosed.get()) { - return - } - if (ongoingSegment.isEmpty()) { - ongoingSegmentFile?.useLines { lines -> - lines.associateTo(ongoingSegment) { - val (k, v) = it.split("=", limit = 2) - k to v + lock.acquire().use { + if (isClosed.get()) { + return + } + if (ongoingSegment.isEmpty()) { + ongoingSegmentFile?.useLines { lines -> + lines.associateTo(ongoingSegment) { + val (k, v) = it.split("=", limit = 2) + k to v + } } } + if (value == null) { + ongoingSegment.remove(key) + } else { + ongoingSegment[key] = value + } + ongoingSegmentFile?.writeText(ongoingSegment.entries.joinToString("\n") { (k, v) -> "$k=$v" }) } - if (value == null) { - ongoingSegment.remove(key) - } else { - ongoingSegment[key] = value - } - ongoingSegmentFile?.writeText(ongoingSegment.entries.joinToString("\n") { (k, v) -> "$k=$v" }) } companion object { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt index 2f4665cd5d..27b4fcef36 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BaseCaptureStrategy.kt @@ -161,7 +161,7 @@ internal abstract class BaseCaptureStrategy( override fun onTouchEvent(event: MotionEvent) { val rrwebEvents = gestureConverter.convert(event, recorderConfig) if (rrwebEvents != null) { - synchronized(currentEventsLock) { + currentEventsLock.acquire().use { currentEvents += rrwebEvents } } diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt index 1f4fc8777e..4ad9f03386 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/CaptureStrategy.kt @@ -17,6 +17,7 @@ import io.sentry.rrweb.RRWebBreadcrumbEvent import io.sentry.rrweb.RRWebEvent import io.sentry.rrweb.RRWebMetaEvent import io.sentry.rrweb.RRWebVideoEvent +import io.sentry.util.AutoClosableReentrantLock import java.io.File import java.util.Date import java.util.LinkedList @@ -56,7 +57,7 @@ internal interface CaptureStrategy { fun close() companion object { - internal val currentEventsLock = Any() + internal val currentEventsLock = AutoClosableReentrantLock() fun createSegment( scopes: IScopes?, @@ -207,7 +208,7 @@ internal interface CaptureStrategy { until: Long, callback: ((RRWebEvent) -> Unit)? = null ) { - synchronized(currentEventsLock) { + currentEventsLock.acquire().use { var event = events.peek() while (event != null && event.timestamp < until) { callback?.invoke(event) diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java index 8f8ab283e9..2d9e5a6bc9 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java @@ -8,11 +8,13 @@ import androidx.compose.ui.semantics.SemanticsModifier; import androidx.compose.ui.semantics.SemanticsPropertyKey; import io.sentry.ILogger; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.compose.SentryComposeHelper; import io.sentry.compose.helper.BuildConfig; import io.sentry.internal.gestures.GestureTargetLocator; import io.sentry.internal.gestures.UiElement; +import io.sentry.util.AutoClosableReentrantLock; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -27,6 +29,7 @@ public final class ComposeGestureTargetLocator implements GestureTargetLocator { private final @NotNull ILogger logger; private volatile @Nullable SentryComposeHelper composeHelper; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public ComposeGestureTargetLocator(final @NotNull ILogger logger) { this.logger = logger; @@ -41,7 +44,7 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) { // lazy init composeHelper as it's using some reflection under the hood if (composeHelper == null) { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (composeHelper == null) { composeHelper = new SentryComposeHelper(logger); } diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporter.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporter.java index 81b843d256..6568b495c3 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporter.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/viewhierarchy/ComposeViewHierarchyExporter.java @@ -9,9 +9,11 @@ import androidx.compose.ui.semantics.SemanticsModifier; import androidx.compose.ui.semantics.SemanticsPropertyKey; import io.sentry.ILogger; +import io.sentry.ISentryLifecycleToken; import io.sentry.compose.SentryComposeHelper; import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; import io.sentry.protocol.ViewHierarchyNode; +import io.sentry.util.AutoClosableReentrantLock; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -23,6 +25,7 @@ public final class ComposeViewHierarchyExporter implements ViewHierarchyExporter @NotNull private final ILogger logger; @Nullable private volatile SentryComposeHelper composeHelper; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public ComposeViewHierarchyExporter(@NotNull final ILogger logger) { this.logger = logger; @@ -37,7 +40,7 @@ public boolean export(@NotNull final ViewHierarchyNode parent, @NotNull final Ob // lazy init composeHelper as it's using some reflection under the hood if (composeHelper == null) { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (composeHelper == null) { composeHelper = new SentryComposeHelper(logger); } diff --git a/sentry-graphql/src/main/java/io/sentry/graphql/SentryGraphqlExceptionHandler.java b/sentry-graphql/src/main/java/io/sentry/graphql/SentryGraphqlExceptionHandler.java index a1f94cacce..7da3dbfc91 100644 --- a/sentry-graphql/src/main/java/io/sentry/graphql/SentryGraphqlExceptionHandler.java +++ b/sentry-graphql/src/main/java/io/sentry/graphql/SentryGraphqlExceptionHandler.java @@ -7,6 +7,8 @@ import graphql.execution.DataFetcherExceptionHandlerParameters; import graphql.execution.DataFetcherExceptionHandlerResult; import graphql.schema.DataFetchingEnvironment; +import io.sentry.ISentryLifecycleToken; +import io.sentry.util.AutoClosableReentrantLock; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; @@ -17,7 +19,8 @@ @ApiStatus.Internal public final class SentryGraphqlExceptionHandler { private final @Nullable DataFetcherExceptionHandler delegate; - private final @NotNull Object exceptionContextLock = new Object(); + private final @NotNull AutoClosableReentrantLock exceptionContextLock = + new AutoClosableReentrantLock(); public SentryGraphqlExceptionHandler(final @Nullable DataFetcherExceptionHandler delegate) { this.delegate = delegate; @@ -30,7 +33,7 @@ public SentryGraphqlExceptionHandler(final @Nullable DataFetcherExceptionHandler if (environment != null) { final @Nullable GraphQLContext graphQlContext = environment.getGraphQlContext(); if (graphQlContext != null) { - synchronized (exceptionContextLock) { + try (final @NotNull ISentryLifecycleToken ignored = exceptionContextLock.acquire()) { final @NotNull List exceptions = graphQlContext.getOrDefault( SENTRY_EXCEPTIONS_CONTEXT_KEY, new CopyOnWriteArrayList()); diff --git a/sentry-jdbc/api/sentry-jdbc.api b/sentry-jdbc/api/sentry-jdbc.api index 700cbb2d69..dba5791f80 100644 --- a/sentry-jdbc/api/sentry-jdbc.api +++ b/sentry-jdbc/api/sentry-jdbc.api @@ -15,6 +15,7 @@ public final class io/sentry/jdbc/DatabaseUtils$DatabaseDetails { } public class io/sentry/jdbc/SentryJdbcEventListener : com/p6spy/engine/event/SimpleJdbcEventListener { + protected final field databaseDetailsLock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun (Lio/sentry/IScopes;)V public fun onAfterAnyExecute (Lcom/p6spy/engine/common/StatementInformation;JLjava/sql/SQLException;)V diff --git a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java index 4f45a67cc9..8a57e1eeca 100644 --- a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java +++ b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java @@ -7,12 +7,14 @@ import com.p6spy.engine.common.StatementInformation; import com.p6spy.engine.event.SimpleJdbcEventListener; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.ISpan; import io.sentry.ScopesAdapter; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.Span; import io.sentry.SpanOptions; import io.sentry.SpanStatus; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.sql.SQLException; import org.jetbrains.annotations.NotNull; @@ -26,7 +28,8 @@ public class SentryJdbcEventListener extends SimpleJdbcEventListener { private static final @NotNull ThreadLocal CURRENT_SPAN = new ThreadLocal<>(); private volatile @Nullable DatabaseUtils.DatabaseDetails cachedDatabaseDetails = null; - private final @NotNull Object databaseDetailsLock = new Object(); + protected final @NotNull AutoClosableReentrantLock databaseDetailsLock = + new AutoClosableReentrantLock(); public SentryJdbcEventListener(final @NotNull IScopes scopes) { this.scopes = Objects.requireNonNull(scopes, "scopes are required"); @@ -92,7 +95,7 @@ private void applyDatabaseDetailsToSpan( private @NotNull DatabaseUtils.DatabaseDetails getOrComputeDatabaseDetails( final @NotNull StatementInformation statementInformation) { if (cachedDatabaseDetails == null) { - synchronized (databaseDetailsLock) { + try (final @NotNull ISentryLifecycleToken ignored = databaseDetailsLock.acquire()) { if (cachedDatabaseDetails == null) { cachedDatabaseDetails = DatabaseUtils.readFrom(statementInformation); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java index 85526b200f..66928c2dee 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java @@ -26,6 +26,7 @@ import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.LazyEvaluator; import io.sentry.util.Objects; import java.lang.ref.WeakReference; @@ -64,6 +65,7 @@ public final class OtelSpanWrapper implements ISpan { private @Nullable String transactionName; private @Nullable TransactionNameSource transactionNameSource; private final @Nullable Baggage baggage; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private final @NotNull Map data = new ConcurrentHashMap<>(); private final @NotNull Map measurements = new ConcurrentHashMap<>(); @@ -203,7 +205,7 @@ public OtelSpanWrapper( } private void updateBaggageValues() { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (baggage != null && baggage.isMutable()) { final AtomicReference replayIdAtomicReference = new AtomicReference<>(); scopes.configureScope( diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java index af9413f74f..7c8e2d3459 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/SentryWeakSpanStorage.java @@ -2,6 +2,8 @@ import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.internal.shaded.WeakConcurrentMap; +import io.sentry.ISentryLifecycleToken; +import io.sentry.util.AutoClosableReentrantLock; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,10 +15,12 @@ @ApiStatus.Internal public final class SentryWeakSpanStorage { private static volatile @Nullable SentryWeakSpanStorage INSTANCE; + private static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); public static @NotNull SentryWeakSpanStorage getInstance() { if (INSTANCE == null) { - synchronized (SentryWeakSpanStorage.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (INSTANCE == null) { INSTANCE = new SentryWeakSpanStorage(); } diff --git a/sentry-spring-jakarta/api/sentry-spring-jakarta.api b/sentry-spring-jakarta/api/sentry-spring-jakarta.api index e445aac284..2897189677 100644 --- a/sentry-spring-jakarta/api/sentry-spring-jakarta.api +++ b/sentry-spring-jakarta/api/sentry-spring-jakarta.api @@ -49,6 +49,7 @@ public class io/sentry/spring/jakarta/SentryRequestHttpServletRequestProcessor : } public class io/sentry/spring/jakarta/SentryRequestResolver { + protected static final field staticLock Lio/sentry/util/AutoClosableReentrantLock; public fun (Lio/sentry/IScopes;)V public fun resolveSentryRequest (Ljakarta/servlet/http/HttpServletRequest;)Lio/sentry/protocol/Request; } diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java index 71f9079f9f..4bb2ad312b 100644 --- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SentryRequestResolver.java @@ -2,8 +2,10 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; import io.sentry.protocol.Request; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HttpUtils; import io.sentry.util.Objects; import io.sentry.util.UrlUtils; @@ -20,6 +22,8 @@ @Open public class SentryRequestResolver { + protected static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); private final @NotNull IScopes scopes; private volatile @Nullable List extraSecurityCookies; @@ -71,7 +75,7 @@ Map resolveHeadersMap( private List extractSecurityCookieNamesOrUseCached( final @NotNull HttpServletRequest httpRequest) { if (extraSecurityCookies == null) { - synchronized (SentryRequestResolver.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (extraSecurityCookies == null) { extraSecurityCookies = extractSecurityCookieNames(httpRequest); } diff --git a/sentry-spring/api/sentry-spring.api b/sentry-spring/api/sentry-spring.api index 9d31cdd62a..2b5bbd98c1 100644 --- a/sentry-spring/api/sentry-spring.api +++ b/sentry-spring/api/sentry-spring.api @@ -49,6 +49,7 @@ public class io/sentry/spring/SentryRequestHttpServletRequestProcessor : io/sent } public class io/sentry/spring/SentryRequestResolver { + protected static final field staticLock Lio/sentry/util/AutoClosableReentrantLock; public fun (Lio/sentry/IScopes;)V public fun resolveSentryRequest (Ljavax/servlet/http/HttpServletRequest;)Lio/sentry/protocol/Request; } diff --git a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java index 2d9e2996f7..56294fda08 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java @@ -2,8 +2,10 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.IScopes; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryLevel; import io.sentry.protocol.Request; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HttpUtils; import io.sentry.util.Objects; import io.sentry.util.UrlUtils; @@ -20,6 +22,8 @@ @Open public class SentryRequestResolver { + protected static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); private final @NotNull IScopes scopes; private volatile @Nullable List extraSecurityCookies; @@ -71,7 +75,7 @@ Map resolveHeadersMap( private List extractSecurityCookieNamesOrUseCached( final @NotNull HttpServletRequest httpRequest) { if (extraSecurityCookies == null) { - synchronized (SentryRequestResolver.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (extraSecurityCookies == null) { extraSecurityCookies = extractSecurityCookieNames(httpRequest); } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ff14481eda..33aa44f06b 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2822,6 +2822,7 @@ public final class io/sentry/SentryNanotimeDateProvider : io/sentry/SentryDatePr public class io/sentry/SentryOptions { public static final field DEFAULT_PROPAGATION_TARGETS Ljava/lang/String; + protected final field lock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun addBundleId (Ljava/lang/String;)V public fun addContextTag (Ljava/lang/String;)V @@ -3831,6 +3832,7 @@ public class io/sentry/cache/EnvelopeCache : io/sentry/cache/IEnvelopeCache { public static final field STARTUP_CRASH_MARKER_FILE Ljava/lang/String; public static final field SUFFIX_ENVELOPE_FILE Ljava/lang/String; protected static final field UTF_8 Ljava/nio/charset/Charset; + protected final field cacheLock Lio/sentry/util/AutoClosableReentrantLock; public fun (Lio/sentry/SentryOptions;Ljava/lang/String;I)V public static fun create (Lio/sentry/SentryOptions;)Lio/sentry/cache/IEnvelopeCache; public fun discard (Lio/sentry/SentryEnvelope;)V @@ -4513,6 +4515,7 @@ public final class io/sentry/protocol/Browser$JsonKeys { public class io/sentry/protocol/Contexts : io/sentry/JsonSerializable { public static final field REPLAY_ID Ljava/lang/String; + protected final field responseLock Lio/sentry/util/AutoClosableReentrantLock; public fun ()V public fun (Lio/sentry/protocol/Contexts;)V public fun containsKey (Ljava/lang/Object;)Z @@ -6132,6 +6135,11 @@ public abstract class io/sentry/transport/TransportResult { public static fun success ()Lio/sentry/transport/TransportResult; } +public final class io/sentry/util/AutoClosableReentrantLock : java/util/concurrent/locks/ReentrantLock { + public fun ()V + public fun acquire ()Lio/sentry/ISentryLifecycleToken; +} + public final class io/sentry/util/CheckInUtils { public fun ()V public static fun isIgnored (Ljava/util/List;Ljava/lang/String;)Z diff --git a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java index 9839569dc2..9489842b5c 100644 --- a/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.ArrayList; import java.util.List; @@ -18,7 +19,7 @@ public final class DefaultTransactionPerformanceCollector implements TransactionPerformanceCollector { private static final long TRANSACTION_COLLECTION_INTERVAL_MILLIS = 100; private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000; - private final @NotNull Object timerLock = new Object(); + private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock(); private volatile @Nullable Timer timer = null; private final @NotNull Map> performanceDataMap = new ConcurrentHashMap<>(); @@ -82,7 +83,7 @@ public void start(final @NotNull ITransaction transaction) { } } if (!isStarted.getAndSet(true)) { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer == null) { timer = new Timer(true); } @@ -181,7 +182,7 @@ public void close() { collector.clear(); } if (isStarted.getAndSet(false)) { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer != null) { timer.cancel(); timer = null; diff --git a/sentry/src/main/java/io/sentry/Hint.java b/sentry/src/main/java/io/sentry/Hint.java index 750017d00d..d7949b3133 100644 --- a/sentry/src/main/java/io/sentry/Hint.java +++ b/sentry/src/main/java/io/sentry/Hint.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -27,6 +28,7 @@ public final class Hint { private final @NotNull Map internalStorage = new HashMap(); private final @NotNull List attachments = new ArrayList<>(); + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private @Nullable Attachment screenshot = null; private @Nullable Attachment viewHierarchy = null; private @Nullable Attachment threadDump = null; @@ -44,30 +46,37 @@ public final class Hint { return hint; } - public synchronized void set(@NotNull String name, @Nullable Object hint) { - internalStorage.put(name, hint); + public void set(@NotNull String name, @Nullable Object hint) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + internalStorage.put(name, hint); + } } - public synchronized @Nullable Object get(@NotNull String name) { - return internalStorage.get(name); + public @Nullable Object get(@NotNull String name) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + return internalStorage.get(name); + } } @SuppressWarnings("unchecked") - public synchronized @Nullable T getAs( - @NotNull String name, @NotNull Class clazz) { - Object hintValue = internalStorage.get(name); - - if (clazz.isInstance(hintValue)) { - return (T) hintValue; - } else if (isCastablePrimitive(hintValue, clazz)) { - return (T) hintValue; - } else { - return null; + public @Nullable T getAs(@NotNull String name, @NotNull Class clazz) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + Object hintValue = internalStorage.get(name); + + if (clazz.isInstance(hintValue)) { + return (T) hintValue; + } else if (isCastablePrimitive(hintValue, clazz)) { + return (T) hintValue; + } else { + return null; + } } } - public synchronized void remove(@NotNull String name) { - internalStorage.remove(name); + public void remove(@NotNull String name) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + internalStorage.remove(name); + } } public void addAttachment(@Nullable Attachment attachment) { @@ -101,13 +110,15 @@ public void clearAttachments() { * referenced. */ @ApiStatus.Internal - public synchronized void clear() { - final Iterator> iterator = internalStorage.entrySet().iterator(); - - while (iterator.hasNext()) { - final Map.Entry entry = iterator.next(); - if (entry.getKey() == null || !entry.getKey().startsWith("sentry:")) { - iterator.remove(); + public void clear() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + final Iterator> iterator = internalStorage.entrySet().iterator(); + + while (iterator.hasNext()) { + final Map.Entry entry = iterator.next(); + if (entry.getKey() == null || !entry.getKey().startsWith("sentry:")) { + iterator.remove(); + } } } } diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index 78658091de..a5cbacc4df 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -7,6 +7,7 @@ import io.sentry.protocol.SentryException; import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HintUtils; import io.sentry.util.Objects; import java.io.Closeable; @@ -27,6 +28,8 @@ public final class MainEventProcessor implements EventProcessor, Closeable { private final @NotNull SentryThreadFactory sentryThreadFactory; private final @NotNull SentryExceptionFactory sentryExceptionFactory; private volatile @Nullable HostnameCache hostnameCache = null; + private final @NotNull AutoClosableReentrantLock hostnameCacheLock = + new AutoClosableReentrantLock(); public MainEventProcessor(final @NotNull SentryOptions options) { this.options = Objects.requireNonNull(options, "The SentryOptions is required."); @@ -201,7 +204,7 @@ private void setServerName(final @NotNull SentryBaseEvent event) { private void ensureHostnameCache() { if (hostnameCache == null) { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = hostnameCacheLock.acquire()) { if (hostnameCache == null) { hostnameCache = HostnameCache.getInstance(); } diff --git a/sentry/src/main/java/io/sentry/MetricsAggregator.java b/sentry/src/main/java/io/sentry/MetricsAggregator.java index ebc634700b..b71446d91c 100644 --- a/sentry/src/main/java/io/sentry/MetricsAggregator.java +++ b/sentry/src/main/java/io/sentry/MetricsAggregator.java @@ -10,6 +10,7 @@ import io.sentry.metrics.MetricType; import io.sentry.metrics.MetricsHelper; import io.sentry.metrics.SetMetric; +import io.sentry.util.AutoClosableReentrantLock; import java.io.Closeable; import java.io.IOException; import java.nio.charset.Charset; @@ -36,6 +37,8 @@ public final class MetricsAggregator implements IMetricsAggregator, Runnable, Cl private final @NotNull IMetricsClient client; private final @NotNull SentryDateProvider dateProvider; private final @Nullable SentryOptions.BeforeEmitMetricCallback beforeEmitCallback; + private final @NotNull AutoClosableReentrantLock aggregatorLock = new AutoClosableReentrantLock(); + private final @NotNull AutoClosableReentrantLock bucketsLock = new AutoClosableReentrantLock(); private volatile @NotNull ISentryExecutorService executorService; private volatile boolean isClosed = false; @@ -215,7 +218,7 @@ private void add( final boolean isOverWeight = isOverWeight(); if (!isClosed && (isOverWeight || !flushScheduled)) { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = aggregatorLock.acquire()) { if (!isClosed) { // TODO this is probably not a good idea after all // as it will slow down the first metric emission @@ -304,7 +307,7 @@ private Map getOrAddTimeBucket(final long bucketKey) { if (bucket == null) { // although buckets is thread safe, we still need to synchronize here to avoid creating // the same bucket at the same time, overwriting each other - synchronized (buckets) { + try (final @NotNull ISentryLifecycleToken ignored = bucketsLock.acquire()) { bucket = buckets.get(bucketKey); if (bucket == null) { bucket = new HashMap<>(); @@ -317,7 +320,7 @@ private Map getOrAddTimeBucket(final long bucketKey) { @Override public void close() throws IOException { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = aggregatorLock.acquire()) { isClosed = true; executorService.close(0); } @@ -329,7 +332,7 @@ public void close() throws IOException { public void run() { flush(false); - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = aggregatorLock.acquire()) { if (!isClosed && !buckets.isEmpty()) { executorService.schedule(this, MetricsHelper.FLUSHER_SLEEP_TIME_MS); } diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index ca4c0e6d7d..74e6642546 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -7,6 +7,7 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.protocol.User; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.CollectionUtils; import io.sentry.util.EventProcessorUtils; import io.sentry.util.ExceptionUtils; @@ -76,13 +77,15 @@ public final class Scope implements IScope { private volatile @Nullable Session session; /** Session lock, Ops should be atomic */ - private final @NotNull Object sessionLock = new Object(); + private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); /** Transaction lock, Ops should be atomic */ - private final @NotNull Object transactionLock = new Object(); + private final @NotNull AutoClosableReentrantLock transactionLock = + new AutoClosableReentrantLock(); /** PropagationContext lock, Ops should be atomic */ - private final @NotNull Object propagationContextLock = new Object(); + private final @NotNull AutoClosableReentrantLock propagationContextLock = + new AutoClosableReentrantLock(); /** Scope's contexts */ private @NotNull Contexts contexts = new Contexts(); @@ -266,7 +269,7 @@ public void setActiveSpan(final @Nullable ISpan span) { */ @Override public void setTransaction(final @Nullable ITransaction transaction) { - synchronized (transactionLock) { + try (final @NotNull ISentryLifecycleToken ignored = transactionLock.acquire()) { this.transaction = transaction; for (final IScopeObserver observer : options.getScopeObservers()) { @@ -510,7 +513,7 @@ public void clearBreadcrumbs() { /** Clears the transaction. */ @Override public void clearTransaction() { - synchronized (transactionLock) { + try (final @NotNull ISentryLifecycleToken ignored = transactionLock.acquire()) { transaction = null; } transactionName = null; @@ -831,7 +834,7 @@ public void addEventProcessor(final @NotNull EventProcessor eventProcessor) { @Override public Session withSession(final @NotNull IWithSession sessionCallback) { Session cloneSession = null; - synchronized (sessionLock) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { sessionCallback.accept(session); if (session != null) { @@ -863,7 +866,7 @@ interface IWithSession { public SessionPair startSession() { Session previousSession; SessionPair pair = null; - synchronized (sessionLock) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { if (session != null) { // Assumes session will NOT flush itself (Not passing any scopes to it) session.end(); @@ -937,7 +940,7 @@ public SessionPair(final @NotNull Session current, final @Nullable Session previ @Override public Session endSession() { Session previousSession = null; - synchronized (sessionLock) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { if (session != null) { session.end(); previousSession = session.clone(); @@ -955,7 +958,7 @@ public Session endSession() { @ApiStatus.Internal @Override public void withTransaction(final @NotNull IWithTransaction callback) { - synchronized (transactionLock) { + try (final @NotNull ISentryLifecycleToken ignored = transactionLock.acquire()) { callback.accept(transaction); } } @@ -1000,7 +1003,7 @@ public void setPropagationContext(final @NotNull PropagationContext propagationC @Override public @NotNull PropagationContext withPropagationContext( final @NotNull IWithPropagationContext callback) { - synchronized (propagationContextLock) { + try (final @NotNull ISentryLifecycleToken ignored = propagationContextLock.acquire()) { callback.accept(propagationContext); return new PropagationContext(propagationContext); } diff --git a/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java index 8234affa05..6f58d426dc 100644 --- a/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java +++ b/sentry/src/main/java/io/sentry/SendCachedEnvelopeFireAndForgetIntegration.java @@ -3,6 +3,7 @@ import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; import io.sentry.transport.RateLimiter; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.io.Closeable; import java.io.File; @@ -23,6 +24,7 @@ public final class SendCachedEnvelopeFireAndForgetIntegration private @Nullable SendFireAndForget sender; private final AtomicBoolean isInitialized = new AtomicBoolean(false); private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); public interface SendFireAndForget { void send(); @@ -101,9 +103,9 @@ public void onConnectionStatusChanged( } @SuppressWarnings({"FutureReturnValueIgnored", "NullAway"}) - private synchronized void sendCachedEnvelopes( + private void sendCachedEnvelopes( final @NotNull IScopes scopes, final @NotNull SentryOptions options) { - try { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { options .getExecutorService() .submit( diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index ddf369888b..d8f361accb 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -15,6 +15,7 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.User; import io.sentry.transport.NoOpEnvelopeCache; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.DebugMetaPropertiesApplier; import io.sentry.util.FileUtils; import io.sentry.util.InitUtil; @@ -76,6 +77,8 @@ private Sentry() {} /** Timestamp used to check old profiles to delete. */ private static final long classCreationTimestamp = System.currentTimeMillis(); + private static final AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); + /** * Returns the current (threads) hub, if none, clones the rootScopes and returns it. * @@ -263,71 +266,75 @@ public static void init(final @NotNull SentryOptions options) { * @param globalHubMode the globalHubMode */ @SuppressWarnings("deprecation") - private static synchronized void init( - final @NotNull SentryOptions options, final boolean globalHubMode) { - - if (!options.getClass().getName().equals("io.sentry.android.core.SentryAndroidOptions") - && Platform.isAndroid()) { - throw new IllegalArgumentException( - "You are running Android. Please, use SentryAndroid.init. " - + options.getClass().getName()); - } + private static void init(final @NotNull SentryOptions options, final boolean globalHubMode) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + + if (!options.getClass().getName().equals("io.sentry.android.core.SentryAndroidOptions") + && Platform.isAndroid()) { + throw new IllegalArgumentException( + "You are running Android. Please, use SentryAndroid.init. " + + options.getClass().getName()); + } - if (!preInitConfigurations(options)) { - return; - } + if (!preInitConfigurations(options)) { + return; + } - options.getLogger().log(SentryLevel.INFO, "GlobalHubMode: '%s'", String.valueOf(globalHubMode)); - Sentry.globalHubMode = globalHubMode; - final boolean shouldInit = InitUtil.shouldInit(globalScope.getOptions(), options, isEnabled()); - if (shouldInit) { - if (isEnabled()) { + options + .getLogger() + .log(SentryLevel.INFO, "GlobalHubMode: '%s'", String.valueOf(globalHubMode)); + Sentry.globalHubMode = globalHubMode; + final boolean shouldInit = + InitUtil.shouldInit(globalScope.getOptions(), options, isEnabled()); + if (shouldInit) { + if (isEnabled()) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "Sentry has been already initialized. Previous configuration will be overwritten."); + } + globalScope.replaceOptions(options); + + final IScopes scopes = getCurrentScopes(); + final IScope rootScope = new Scope(options); + final IScope rootIsolationScope = new Scope(options); + rootScopes = new Scopes(rootScope, rootIsolationScope, globalScope, "Sentry.init"); + + getScopesStorage().set(rootScopes); + + scopes.close(true); + + initConfigurations(options); + + globalScope.bindClient(new SentryClient(options)); + + // If the executorService passed in the init is the same that was previously closed, we have + // to + // set a new one + if (options.getExecutorService().isClosed()) { + options.setExecutorService(new SentryExecutorService()); + } + // when integrations are registered on Scopes ctor and async integrations are fired, + // it might and actually happened that integrations called captureSomething + // and Scopes was still NoOp. + // Registering integrations here make sure that Scopes is already created. + for (final Integration integration : options.getIntegrations()) { + integration.register(ScopesAdapter.getInstance(), options); + } + + notifyOptionsObservers(options); + + finalizePreviousSession(options, ScopesAdapter.getInstance()); + + handleAppStartProfilingConfig(options, options.getExecutorService()); + } else { options .getLogger() .log( SentryLevel.WARNING, - "Sentry has been already initialized. Previous configuration will be overwritten."); + "This init call has been ignored due to priority being too low."); } - globalScope.replaceOptions(options); - - final IScopes scopes = getCurrentScopes(); - final IScope rootScope = new Scope(options); - final IScope rootIsolationScope = new Scope(options); - rootScopes = new Scopes(rootScope, rootIsolationScope, globalScope, "Sentry.init"); - - getScopesStorage().set(rootScopes); - - scopes.close(true); - - initConfigurations(options); - - globalScope.bindClient(new SentryClient(options)); - - // If the executorService passed in the init is the same that was previously closed, we have - // to - // set a new one - if (options.getExecutorService().isClosed()) { - options.setExecutorService(new SentryExecutorService()); - } - // when integrations are registered on Scopes ctor and async integrations are fired, - // it might and actually happened that integrations called captureSomething - // and Scopes was still NoOp. - // Registering integrations here make sure that Scopes is already created. - for (final Integration integration : options.getIntegrations()) { - integration.register(ScopesAdapter.getInstance(), options); - } - - notifyOptionsObservers(options); - - finalizePreviousSession(options, ScopesAdapter.getInstance()); - - handleAppStartProfilingConfig(options, options.getExecutorService()); - } else { - options - .getLogger() - .log( - SentryLevel.WARNING, - "This init call has been ignored due to priority being too low."); } } @@ -556,12 +563,14 @@ private static void initConfigurations(final @NotNull SentryOptions options) { } /** Close the SDK */ - public static synchronized void close() { - final IScopes scopes = getCurrentScopes(); - rootScopes = NoOpScopes.getInstance(); - // remove thread local to avoid memory leak - getScopesStorage().close(); - scopes.close(false); + public static void close() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + final IScopes scopes = getCurrentScopes(); + rootScopes = NoOpScopes.getInstance(); + // remove thread local to avoid memory leak + getScopesStorage().close(); + scopes.close(false); + } } /** diff --git a/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java b/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java index 5574bc263e..dc5b334df2 100644 --- a/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java +++ b/sentry/src/main/java/io/sentry/SentryCrashLastRunState.java @@ -1,6 +1,7 @@ package io.sentry; import io.sentry.cache.EnvelopeCache; +import io.sentry.util.AutoClosableReentrantLock; import java.io.File; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -15,7 +16,8 @@ public final class SentryCrashLastRunState { private boolean readCrashedLastRun; private @Nullable Boolean crashedLastRun; - private final @NotNull Object crashedLastRunLock = new Object(); + private final @NotNull AutoClosableReentrantLock crashedLastRunLock = + new AutoClosableReentrantLock(); private SentryCrashLastRunState() {} @@ -25,7 +27,7 @@ public static SentryCrashLastRunState getInstance() { public @Nullable Boolean isCrashedLastRun( final @Nullable String cacheDirPath, final boolean deleteFile) { - synchronized (crashedLastRunLock) { + try (final @NotNull ISentryLifecycleToken ignored = crashedLastRunLock.acquire()) { if (readCrashedLastRun) { return crashedLastRun; } @@ -63,7 +65,7 @@ public static SentryCrashLastRunState getInstance() { } public void setCrashedLastRun(final boolean crashedLastRun) { - synchronized (crashedLastRunLock) { + try (final @NotNull ISentryLifecycleToken ignored = crashedLastRunLock.acquire()) { if (!readCrashedLastRun) { this.crashedLastRun = crashedLastRun; // mark readCrashedLastRun as true since its being set directly @@ -74,7 +76,7 @@ public void setCrashedLastRun(final boolean crashedLastRun) { @TestOnly public void reset() { - synchronized (crashedLastRunLock) { + try (final @NotNull ISentryLifecycleToken ignored = crashedLastRunLock.acquire()) { readCrashedLastRun = false; crashedLastRun = null; } diff --git a/sentry/src/main/java/io/sentry/SentryExecutorService.java b/sentry/src/main/java/io/sentry/SentryExecutorService.java index 4aa903b350..bb08e51b3a 100644 --- a/sentry/src/main/java/io/sentry/SentryExecutorService.java +++ b/sentry/src/main/java/io/sentry/SentryExecutorService.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -14,6 +15,7 @@ public final class SentryExecutorService implements ISentryExecutorService { private final @NotNull ScheduledExecutorService executorService; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); @TestOnly SentryExecutorService(final @NotNull ScheduledExecutorService executorService) { @@ -41,7 +43,7 @@ public SentryExecutorService() { @Override public void close(final long timeoutMillis) { - synchronized (executorService) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (!executorService.isShutdown()) { executorService.shutdown(); try { @@ -58,7 +60,7 @@ public void close(final long timeoutMillis) { @Override public boolean isClosed() { - synchronized (executorService) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return executorService.isShutdown(); } } diff --git a/sentry/src/main/java/io/sentry/SentryIntegrationPackageStorage.java b/sentry/src/main/java/io/sentry/SentryIntegrationPackageStorage.java index e2ca7e9ac6..1653429144 100644 --- a/sentry/src/main/java/io/sentry/SentryIntegrationPackageStorage.java +++ b/sentry/src/main/java/io/sentry/SentryIntegrationPackageStorage.java @@ -1,6 +1,7 @@ package io.sentry; import io.sentry.protocol.SentryPackage; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -12,10 +13,12 @@ @ApiStatus.Internal public final class SentryIntegrationPackageStorage { private static volatile @Nullable SentryIntegrationPackageStorage INSTANCE; + private static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); public static @NotNull SentryIntegrationPackageStorage getInstance() { if (INSTANCE == null) { - synchronized (SentryIntegrationPackageStorage.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (INSTANCE == null) { INSTANCE = new SentryIntegrationPackageStorage(); } diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 764dff1851..caac941cdb 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -19,6 +19,7 @@ import io.sentry.transport.ITransportGate; import io.sentry.transport.NoOpEnvelopeCache; import io.sentry.transport.NoOpTransportGate; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Platform; import io.sentry.util.SampleRateUtils; import io.sentry.util.StringUtils; @@ -503,6 +504,8 @@ public class SentryOptions { private boolean forceInit = false; + protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); + /** * Adds an event processor * @@ -981,7 +984,7 @@ public void setTracesSampler(final @Nullable TracesSamplerCallback tracesSampler @ApiStatus.Internal public @NotNull TracesSampler getInternalTracesSampler() { if (internalTracesSampler == null) { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (internalTracesSampler == null) { internalTracesSampler = new TracesSampler(this); } diff --git a/sentry/src/main/java/io/sentry/SentrySpanStorage.java b/sentry/src/main/java/io/sentry/SentrySpanStorage.java index eb7379741c..56397971e5 100644 --- a/sentry/src/main/java/io/sentry/SentrySpanStorage.java +++ b/sentry/src/main/java/io/sentry/SentrySpanStorage.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.ApiStatus; @@ -16,10 +17,12 @@ @ApiStatus.Internal public final class SentrySpanStorage { private static volatile @Nullable SentrySpanStorage INSTANCE; + private static final @NotNull AutoClosableReentrantLock staticLock = + new AutoClosableReentrantLock(); public static @NotNull SentrySpanStorage getInstance() { if (INSTANCE == null) { - synchronized (SentrySpanStorage.class) { + try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) { if (INSTANCE == null) { INSTANCE = new SentrySpanStorage(); } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index bb801dbac6..0780b57a38 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -5,6 +5,7 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.TransactionNameSource; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import io.sentry.util.SpanUtils; import java.util.ArrayList; @@ -40,7 +41,8 @@ public final class SentryTracer implements ITransaction { private volatile @Nullable TimerTask deadlineTimeoutTask; private volatile @Nullable Timer timer = null; - private final @NotNull Object timerLock = new Object(); + private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock(); + private final @NotNull AutoClosableReentrantLock tracerLock = new AutoClosableReentrantLock(); private final @NotNull AtomicBoolean isIdleFinishTimerRunning = new AtomicBoolean(false); private final @NotNull AtomicBoolean isDeadlineTimerRunning = new AtomicBoolean(false); @@ -103,7 +105,7 @@ public SentryTracer( @Override public void scheduleFinish() { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer != null) { final @Nullable Long idleTimeout = transactionOptions.getIdleTimeout(); @@ -254,7 +256,7 @@ public void finish( final SentryTransaction transaction = new SentryTransaction(this); if (timer != null) { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer != null) { cancelIdleTimer(); cancelDeadlineTimer(); @@ -282,7 +284,7 @@ public void finish( } private void cancelIdleTimer() { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (idleTimeoutTask != null) { idleTimeoutTask.cancel(); isIdleFinishTimerRunning.set(false); @@ -294,7 +296,7 @@ private void cancelIdleTimer() { private void scheduleDeadlineTimeout() { final @Nullable Long deadlineTimeOut = transactionOptions.getDeadlineTimeout(); if (deadlineTimeOut != null) { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (timer != null) { cancelDeadlineTimer(); isDeadlineTimerRunning.set(true); @@ -322,7 +324,7 @@ public void run() { } private void cancelDeadlineTimer() { - synchronized (timerLock) { + try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) { if (deadlineTimeoutTask != null) { deadlineTimeoutTask.cancel(); isDeadlineTimerRunning.set(false); @@ -648,7 +650,7 @@ public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) } private void updateBaggageValues() { - synchronized (this) { + try (final @NotNull ISentryLifecycleToken ignored = tracerLock.acquire()) { if (baggage.isMutable()) { final AtomicReference replayId = new AtomicReference<>(); scopes.configureScope( diff --git a/sentry/src/main/java/io/sentry/Session.java b/sentry/src/main/java/io/sentry/Session.java index 482b055b67..5ea37a74cd 100644 --- a/sentry/src/main/java/io/sentry/Session.java +++ b/sentry/src/main/java/io/sentry/Session.java @@ -1,6 +1,7 @@ package io.sentry; import io.sentry.protocol.User; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.StringUtils; import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; @@ -67,7 +68,7 @@ public enum State { private @Nullable String abnormalMechanism; /** The session lock, ops should be atomic */ - private final @NotNull Object sessionLock = new Object(); + private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); @SuppressWarnings("unused") private @Nullable Map unknown; @@ -208,7 +209,7 @@ public void end() { * @param timestamp the timestamp or null */ public void end(final @Nullable Date timestamp) { - synchronized (sessionLock) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { init = null; // at this state it might be Crashed already, so we don't check for it. @@ -262,7 +263,7 @@ public boolean update( final @Nullable String userAgent, final boolean addErrorsCount, final @Nullable String abnormalMechanism) { - synchronized (sessionLock) { + try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { boolean sessionHasBeenUpdated = false; if (status != null) { this.status = status; diff --git a/sentry/src/main/java/io/sentry/Stack.java b/sentry/src/main/java/io/sentry/Stack.java index adb43f8212..9b768ad274 100644 --- a/sentry/src/main/java/io/sentry/Stack.java +++ b/sentry/src/main/java/io/sentry/Stack.java @@ -1,11 +1,13 @@ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.Objects; import java.util.Deque; import java.util.Iterator; import java.util.concurrent.LinkedBlockingDeque; import org.jetbrains.annotations.NotNull; +/** TODO [POTEL] can this class be removed? */ final class Stack { static final class StackItem { @@ -47,6 +49,7 @@ public void setClient(final @NotNull ISentryClient client) { private final @NotNull Deque items = new LinkedBlockingDeque<>(); private final @NotNull ILogger logger; + private final @NotNull AutoClosableReentrantLock itemsLock = new AutoClosableReentrantLock(); public Stack(final @NotNull ILogger logger, final @NotNull StackItem rootStackItem) { this.logger = Objects.requireNonNull(logger, "logger is required"); @@ -73,7 +76,7 @@ StackItem peek() { } void pop() { - synchronized (items) { + try (final @NotNull ISentryLifecycleToken ignored = itemsLock.acquire()) { if (items.size() != 1) { items.pop(); } else { diff --git a/sentry/src/main/java/io/sentry/SynchronizedCollection.java b/sentry/src/main/java/io/sentry/SynchronizedCollection.java index 8693b45eca..e7eccebf63 100644 --- a/sentry/src/main/java/io/sentry/SynchronizedCollection.java +++ b/sentry/src/main/java/io/sentry/SynchronizedCollection.java @@ -19,9 +19,11 @@ package io.sentry; import com.jakewharton.nopen.annotation.Open; +import io.sentry.util.AutoClosableReentrantLock; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; +import org.jetbrains.annotations.NotNull; /** * Decorates another {@link Collection} to synchronize its behaviour for a multi-threaded @@ -50,7 +52,7 @@ class SynchronizedCollection implements Collection, Serializable { /** The collection to decorate */ private final Collection collection; /** The object to lock on, needed for List/SortedSet views */ - final Object lock; + final AutoClosableReentrantLock lock; /** * Factory method to create a synchronized collection. @@ -77,7 +79,7 @@ public static SynchronizedCollection synchronizedCollection(final Collect throw new NullPointerException("Collection must not be null."); } this.collection = collection; - this.lock = this; + this.lock = new AutoClosableReentrantLock(); } /** @@ -87,7 +89,7 @@ public static SynchronizedCollection synchronizedCollection(final Collect * @param lock the lock object to use, must not be null * @throws NullPointerException if the collection or lock is null */ - SynchronizedCollection(final Collection collection, final Object lock) { + SynchronizedCollection(final Collection collection, final AutoClosableReentrantLock lock) { if (collection == null) { throw new NullPointerException("Collection must not be null."); } @@ -111,42 +113,42 @@ protected Collection decorated() { @Override public boolean add(final E object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().add(object); } } @Override public boolean addAll(final Collection coll) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().addAll(coll); } } @Override public void clear() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { decorated().clear(); } } @Override public boolean contains(final Object object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().contains(object); } } @Override public boolean containsAll(final Collection coll) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().containsAll(coll); } } @Override public boolean isEmpty() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().isEmpty(); } } @@ -170,42 +172,42 @@ public Iterator iterator() { @Override public Object[] toArray() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().toArray(); } } @Override public T[] toArray(final T[] object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().toArray(object); } } @Override public boolean remove(final Object object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().remove(object); } } @Override public boolean removeAll(final Collection coll) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().removeAll(coll); } } @Override public boolean retainAll(final Collection coll) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().retainAll(coll); } } @Override public int size() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().size(); } } @@ -213,7 +215,7 @@ public int size() { @SuppressWarnings("UndefinedEquals") @Override public boolean equals(final Object object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (object == this) { return true; } @@ -223,14 +225,14 @@ public boolean equals(final Object object) { @Override public int hashCode() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().hashCode(); } } @Override public String toString() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().toString(); } } diff --git a/sentry/src/main/java/io/sentry/SynchronizedQueue.java b/sentry/src/main/java/io/sentry/SynchronizedQueue.java index 68b75c1953..8e9964a203 100644 --- a/sentry/src/main/java/io/sentry/SynchronizedQueue.java +++ b/sentry/src/main/java/io/sentry/SynchronizedQueue.java @@ -18,7 +18,9 @@ */ package io.sentry; +import io.sentry.util.AutoClosableReentrantLock; import java.util.Queue; +import org.jetbrains.annotations.NotNull; /** * Decorates another {@link Queue} to synchronize its behaviour for a multi-threaded environment. @@ -65,7 +67,7 @@ private SynchronizedQueue(final Queue queue) { * @throws NullPointerException if queue or lock is null */ @SuppressWarnings("ProtectedMembersInFinalClass") - protected SynchronizedQueue(final Queue queue, final Object lock) { + protected SynchronizedQueue(final Queue queue, final AutoClosableReentrantLock lock) { super(queue, lock); } @@ -81,7 +83,7 @@ protected Queue decorated() { @Override public E element() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().element(); } } @@ -92,7 +94,7 @@ public boolean equals(final Object object) { if (object == this) { return true; } - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().equals(object); } } @@ -101,49 +103,49 @@ public boolean equals(final Object object) { @Override public int hashCode() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().hashCode(); } } @Override public boolean offer(final E e) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().offer(e); } } @Override public E peek() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().peek(); } } @Override public E poll() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().poll(); } } @Override public E remove() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().remove(); } } @Override public Object[] toArray() { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().toArray(); } } @Override public T[] toArray(T[] object) { - synchronized (lock) { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { return decorated().toArray(object); } } diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index 3be857a4b2..3a85fb6eb2 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -9,6 +9,7 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.DateUtils; import io.sentry.Hint; +import io.sentry.ISentryLifecycleToken; import io.sentry.SentryCrashLastRunState; import io.sentry.SentryEnvelope; import io.sentry.SentryEnvelopeItem; @@ -21,6 +22,7 @@ import io.sentry.hints.SessionEnd; import io.sentry.hints.SessionStart; import io.sentry.transport.NoOpEnvelopeCache; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HintUtils; import io.sentry.util.Objects; import java.io.BufferedInputStream; @@ -70,6 +72,7 @@ public class EnvelopeCache extends CacheStrategy implements IEnvelopeCache { private final CountDownLatch previousSessionLatch; private final @NotNull Map fileNameMap = new WeakHashMap<>(); + protected final @NotNull AutoClosableReentrantLock cacheLock = new AutoClosableReentrantLock(); public static @NotNull IEnvelopeCache create(final @NotNull SentryOptions options) { final String cacheDirPath = options.getCacheDirPath(); @@ -359,16 +362,18 @@ public void discard(final @NotNull SentryEnvelope envelope) { * @param envelope the SentryEnvelope object * @return the file */ - private synchronized @NotNull File getEnvelopeFile(final @NotNull SentryEnvelope envelope) { - final @NotNull String fileName; - if (fileNameMap.containsKey(envelope)) { - fileName = fileNameMap.get(envelope); - } else { - fileName = UUID.randomUUID() + SUFFIX_ENVELOPE_FILE; - fileNameMap.put(envelope, fileName); - } + private @NotNull File getEnvelopeFile(final @NotNull SentryEnvelope envelope) { + try (final @NotNull ISentryLifecycleToken ignored = cacheLock.acquire()) { + final @NotNull String fileName; + if (fileNameMap.containsKey(envelope)) { + fileName = fileNameMap.get(envelope); + } else { + fileName = UUID.randomUUID() + SUFFIX_ENVELOPE_FILE; + fileNameMap.put(envelope, fileName); + } - return new File(directory.getAbsolutePath(), fileName); + return new File(directory.getAbsolutePath(), fileName); + } } public static @NotNull File getCurrentSessionFile(final @NotNull String cacheDirPath) { diff --git a/sentry/src/main/java/io/sentry/metrics/LocalMetricsAggregator.java b/sentry/src/main/java/io/sentry/metrics/LocalMetricsAggregator.java index 2afbf8e19e..47f1fe60de 100644 --- a/sentry/src/main/java/io/sentry/metrics/LocalMetricsAggregator.java +++ b/sentry/src/main/java/io/sentry/metrics/LocalMetricsAggregator.java @@ -1,7 +1,9 @@ package io.sentry.metrics; +import io.sentry.ISentryLifecycleToken; import io.sentry.MeasurementUnit; import io.sentry.protocol.MetricSummary; +import io.sentry.util.AutoClosableReentrantLock; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -21,6 +23,7 @@ public final class LocalMetricsAggregator { // format: > private final @NotNull Map> buckets = new HashMap<>(); + private final @NotNull AutoClosableReentrantLock bucketsLock = new AutoClosableReentrantLock(); public void add( final @NotNull String bucketKey, @@ -32,7 +35,7 @@ public void add( final @NotNull String exportKey = MetricsHelper.getExportKey(type, key, unit); - synchronized (buckets) { + try (final @NotNull ISentryLifecycleToken ignored = bucketsLock.acquire()) { @Nullable Map bucket = buckets.get(exportKey); //noinspection Java8MapApi if (bucket == null) { @@ -53,7 +56,7 @@ public void add( @NotNull public Map> getSummaries() { final @NotNull Map> summaries = new HashMap<>(); - synchronized (buckets) { + try (final @NotNull ISentryLifecycleToken ignored = bucketsLock.acquire()) { for (final @NotNull Map.Entry> entry : buckets.entrySet()) { final @NotNull String exportKey = Objects.requireNonNull(entry.getKey()); final @NotNull List metricSummaries = new ArrayList<>(); diff --git a/sentry/src/main/java/io/sentry/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java index ba49e91519..fcfb35eb43 100644 --- a/sentry/src/main/java/io/sentry/protocol/Contexts.java +++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java @@ -2,11 +2,13 @@ import com.jakewharton.nopen.annotation.Open; import io.sentry.ILogger; +import io.sentry.ISentryLifecycleToken; import io.sentry.JsonDeserializer; import io.sentry.JsonSerializable; import io.sentry.ObjectReader; import io.sentry.ObjectWriter; import io.sentry.SpanContext; +import io.sentry.util.AutoClosableReentrantLock; import io.sentry.util.HintUtils; import io.sentry.util.Objects; import io.sentry.vendor.gson.stream.JsonToken; @@ -29,7 +31,7 @@ public class Contexts implements JsonSerializable { new ConcurrentHashMap<>(); /** Response lock, Ops should be atomic */ - private final @NotNull Object responseLock = new Object(); + protected final @NotNull AutoClosableReentrantLock responseLock = new AutoClosableReentrantLock(); public Contexts() {} @@ -128,7 +130,7 @@ public void setGpu(final @NotNull Gpu gpu) { } public void withResponse(HintUtils.SentryConsumer callback) { - synchronized (responseLock) { + try (final @NotNull ISentryLifecycleToken ignored = responseLock.acquire()) { final @Nullable Response response = getResponse(); if (response != null) { callback.accept(response); @@ -141,7 +143,7 @@ public void withResponse(HintUtils.SentryConsumer callback) { } public void setResponse(final @NotNull Response response) { - synchronized (responseLock) { + try (final @NotNull ISentryLifecycleToken ignored = responseLock.acquire()) { this.put(Response.TYPE, response); } } diff --git a/sentry/src/main/java/io/sentry/util/AutoClosableReentrantLock.java b/sentry/src/main/java/io/sentry/util/AutoClosableReentrantLock.java new file mode 100644 index 0000000000..2a95a58b5f --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/AutoClosableReentrantLock.java @@ -0,0 +1,29 @@ +package io.sentry.util; + +import io.sentry.ISentryLifecycleToken; +import java.util.concurrent.locks.ReentrantLock; +import org.jetbrains.annotations.NotNull; + +public final class AutoClosableReentrantLock extends ReentrantLock { + + private static final long serialVersionUID = -3283069816958445549L; + + public ISentryLifecycleToken acquire() { + lock(); + return new AutoClosableReentrantLockLifecycleToken(this); + } + + static final class AutoClosableReentrantLockLifecycleToken implements ISentryLifecycleToken { + + private final @NotNull ReentrantLock lock; + + AutoClosableReentrantLockLifecycleToken(final @NotNull ReentrantLock lock) { + this.lock = lock; + } + + @Override + public void close() { + lock.unlock(); + } + } +} diff --git a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java index 5db376e710..bd9ea81321 100644 --- a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java +++ b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java @@ -1,5 +1,6 @@ package io.sentry.util; +import io.sentry.ISentryLifecycleToken; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,6 +13,7 @@ public final class LazyEvaluator { private @Nullable T value = null; private final @NotNull Evaluator evaluator; + private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); /** * Class that evaluates a function lazily. It means the evaluator function is called only when @@ -28,11 +30,13 @@ public LazyEvaluator(final @NotNull Evaluator evaluator) { * * @return The result of the evaluator function. */ - public synchronized @NotNull T getValue() { - if (value == null) { - value = evaluator.evaluate(); + public @NotNull T getValue() { + try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { + if (value == null) { + value = evaluator.evaluate(); + } + return value; } - return value; } public interface Evaluator { diff --git a/sentry/src/test/java/io/sentry/util/AutoClosableReentrantLockTest.kt b/sentry/src/test/java/io/sentry/util/AutoClosableReentrantLockTest.kt new file mode 100644 index 0000000000..1f3c853c6a --- /dev/null +++ b/sentry/src/test/java/io/sentry/util/AutoClosableReentrantLockTest.kt @@ -0,0 +1,17 @@ +package io.sentry.util + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class AutoClosableReentrantLockTest { + + @Test + fun `calls lock in acquire and unlock on close`() { + val lock = AutoClosableReentrantLock() + lock.acquire().use { + assertTrue(lock.isLocked) + } + assertFalse(lock.isLocked) + } +} From 5618a31ccee8509b15cf140ed2e6797bc8360b4b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 25 Sep 2024 06:31:08 +0200 Subject: [PATCH 2/2] Remove deprecated reportFullDisplayed --- sentry/api/sentry.api | 2 -- sentry/src/main/java/io/sentry/IScopes.java | 8 -------- sentry/src/main/java/io/sentry/Sentry.java | 9 --------- sentry/src/test/java/io/sentry/ScopesTest.kt | 7 ------- sentry/src/test/java/io/sentry/SentryTest.kt | 11 ----------- 5 files changed, 37 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 33aa44f06b..a01318a8b8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -901,7 +901,6 @@ public abstract interface class io/sentry/IScopes { public abstract fun pushScope ()Lio/sentry/ISentryLifecycleToken; public abstract fun removeExtra (Ljava/lang/String;)V public abstract fun removeTag (Ljava/lang/String;)V - public fun reportFullDisplayed ()V public abstract fun reportFullyDisplayed ()V public abstract fun setActiveSpan (Lio/sentry/ISpan;)V public abstract fun setExtra (Ljava/lang/String;Ljava/lang/String;)V @@ -2370,7 +2369,6 @@ public final class io/sentry/Sentry { public static fun pushScope ()Lio/sentry/ISentryLifecycleToken; public static fun removeExtra (Ljava/lang/String;)V public static fun removeTag (Ljava/lang/String;)V - public static fun reportFullDisplayed ()V public static fun reportFullyDisplayed ()V public static fun setCurrentHub (Lio/sentry/IHub;)Lio/sentry/ISentryLifecycleToken; public static fun setCurrentScopes (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken; diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index f85f2b7c4a..8c95f2a864 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -657,14 +657,6 @@ void setSpanContext( */ void reportFullyDisplayed(); - /** - * @deprecated See {@link IScopes#reportFullyDisplayed()}. - */ - @Deprecated - default void reportFullDisplayed() { - reportFullyDisplayed(); - } - /** * Continue a trace based on HTTP header values. If no "sentry-trace" header is provided a random * trace ID and span ID is created. diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index d8f361accb..60f7103503 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -1085,15 +1085,6 @@ public static void reportFullyDisplayed() { getCurrentScopes().reportFullyDisplayed(); } - /** - * @deprecated See {@link Sentry#reportFullyDisplayed()}. - */ - @Deprecated - @SuppressWarnings("InlineMeSuggester") - public static void reportFullDisplayed() { - reportFullyDisplayed(); - } - /** the metrics API for the current Scopes */ @NotNull @ApiStatus.Experimental diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index e23a0675a9..afe50c05ee 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -1975,13 +1975,6 @@ class ScopesTest { assertTrue(called) } - @Test - fun `reportFullDisplayed calls reportFullyDisplayed`() { - val scopes = spy(generateScopes()) - scopes.reportFullDisplayed() - verify(scopes).reportFullyDisplayed() - } - @Test fun `continueTrace creates propagation context from headers and returns transaction context if performance enabled`() { val scopes = generateScopes() diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index c6be1c3ef7..e6d468067b 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -714,17 +714,6 @@ class SentryTest { verify(scopes).reportFullyDisplayed() } - @Test - fun `reportFullDisplayed calls reportFullyDisplayed`() { - val scopes = mock() - Sentry.init { - it.dsn = dsn - } - Sentry.setCurrentScopes(scopes) - Sentry.reportFullDisplayed() - verify(scopes).reportFullyDisplayed() - } - @Test fun `ignores executorService if it is closed`() { var sentryOptions: SentryOptions? = null