From 375feeb73946509e6c0f966efd163883fc58ae4c Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 22 Nov 2024 16:40:18 +0100 Subject: [PATCH 1/4] continuous profiler now doesn't start if offline or rate limited continuous profiler stops when rate limited continuous profiler prevents sending chunks after being closed added profile_chunk rate limit --- .../api/sentry-android-core.api | 3 +- .../core/AndroidContinuousProfiler.java | 47 ++++++++++++- .../core/AndroidContinuousProfilerTest.kt | 70 +++++++++++++++++++ .../api/sentry-opentelemetry-bootstrap.api | 2 +- sentry/api/sentry.api | 1 + .../src/main/java/io/sentry/DataCategory.java | 1 + .../clientreport/ClientReportRecorder.java | 3 + .../java/io/sentry/transport/RateLimiter.java | 2 + .../io/sentry/transport/RateLimiterTest.kt | 23 +++++- 9 files changed, 147 insertions(+), 5 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index dd4cfdf3530..b1b45357969 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -36,11 +36,12 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V } -public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler { +public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver { public fun (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/ISentryExecutorService;)V public fun close ()V public fun getProfilerId ()Lio/sentry/protocol/SentryId; public fun isRunning ()Z + public fun onRateLimitChanged (Lio/sentry/transport/RateLimiter;)V public fun start ()V public fun stop ()V } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 0536dcabc72..c4d2e09c08c 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -1,10 +1,13 @@ package io.sentry.android.core; +import static io.sentry.DataCategory.All; +import static io.sentry.IConnectionStatusProvider.ConnectionStatus.DISCONNECTED; import static java.util.concurrent.TimeUnit.SECONDS; import android.annotation.SuppressLint; import android.os.Build; import io.sentry.CompositePerformanceCollector; +import io.sentry.DataCategory; import io.sentry.IContinuousProfiler; import io.sentry.ILogger; import io.sentry.IScopes; @@ -17,6 +20,7 @@ import io.sentry.SentryOptions; import io.sentry.android.core.internal.util.SentryFrameMetricsCollector; import io.sentry.protocol.SentryId; +import io.sentry.transport.RateLimiter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; @@ -27,7 +31,8 @@ import org.jetbrains.annotations.VisibleForTesting; @ApiStatus.Internal -public class AndroidContinuousProfiler implements IContinuousProfiler { +public class AndroidContinuousProfiler + implements IContinuousProfiler, RateLimiter.IRateLimitObserver { private static final long MAX_CHUNK_DURATION_MILLIS = 10000; private final @NotNull ILogger logger; @@ -45,6 +50,7 @@ public class AndroidContinuousProfiler implements IContinuousProfiler { private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; + private boolean isClosed = false; public AndroidContinuousProfiler( final @NotNull BuildInfoProvider buildInfoProvider, @@ -91,11 +97,15 @@ private void init() { } public synchronized void start() { - if ((scopes == null || scopes != NoOpScopes.getInstance()) + if ((scopes == null || scopes == NoOpScopes.getInstance()) && Sentry.getCurrentScopes() != NoOpScopes.getInstance()) { this.scopes = Sentry.getCurrentScopes(); this.performanceCollector = Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector(); + final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); + if (rateLimiter != null) { + rateLimiter.addRateLimitObserver(this); + } } // Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler @@ -109,6 +119,22 @@ public synchronized void start() { return; } + if (scopes != null) { + final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter(); + if (rateLimiter != null + && (rateLimiter.isActiveForCategory(All) + || rateLimiter.isActiveForCategory(DataCategory.ProfileChunk))) { + logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); + return; + } + + // If device is offline, we don't start the profiler, to avoid flooding the cache + if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) { + logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler."); + return; + } + } + final AndroidProfiler.ProfileStartData startData = profiler.start(); // check if profiling started if (startData == null) { @@ -203,6 +229,7 @@ private synchronized void stop(final boolean restartProfiler) { public synchronized void close() { stop(); + isClosed = true; } @Override @@ -216,6 +243,10 @@ private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOpti .getExecutorService() .submit( () -> { + // SDK is closed, we don't send the chunks + if (isClosed) { + return; + } final ArrayList payloads = new ArrayList<>(payloadBuilders.size()); synchronized (payloadBuilders) { for (ProfileChunk.Builder builder : payloadBuilders) { @@ -242,4 +273,16 @@ public boolean isRunning() { Future getStopFuture() { return stopFuture; } + + @Override + public void onRateLimitChanged(@NotNull RateLimiter rateLimiter) { + // We stop the profiler as soon as we are rate limited, to avoid the performance overhead + if (rateLimiter.isActiveForCategory(All) + || rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)) { + logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); + stop(); + } + // If we are not rate limited anymore, we don't do anything: the profile is broken, so it's + // useless to restart it automatically + } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index 70ed75186c9..3fa6bd7da2e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -6,6 +6,8 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.CompositePerformanceCollector import io.sentry.CpuCollectionData +import io.sentry.DataCategory +import io.sentry.IConnectionStatusProvider import io.sentry.ILogger import io.sentry.IScopes import io.sentry.ISentryExecutorService @@ -20,6 +22,7 @@ import io.sentry.android.core.internal.util.SentryFrameMetricsCollector import io.sentry.profilemeasurements.ProfileMeasurement import io.sentry.test.DeferredExecutorService import io.sentry.test.getProperty +import io.sentry.transport.RateLimiter import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.check @@ -394,4 +397,71 @@ class AndroidContinuousProfilerTest { executorService.runAll() verify(fixture.scopes, times(2)).captureProfileChunk(any()) } + + @Test + fun `profiler does not send chunks after close`() { + val executorService = DeferredExecutorService() + val profiler = fixture.getSut { + it.executorService = executorService + } + profiler.start() + assertTrue(profiler.isRunning) + + // We close the profiler, which should prevent sending additional chunks + profiler.close() + + // The executor used to send the chunk doesn't do anything + executorService.runAll() + verify(fixture.scopes, never()).captureProfileChunk(any()) + } + + @Test + fun `profiler stops when rate limited`() { + val executorService = DeferredExecutorService() + val profiler = fixture.getSut { + it.executorService = executorService + } + val rateLimiter = mock() + whenever(rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)).thenReturn(true) + + profiler.start() + assertTrue(profiler.isRunning) + + // If the SDK is rate limited, the profiler should stop + profiler.onRateLimitChanged(rateLimiter) + assertFalse(profiler.isRunning) + verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler.")) + } + + @Test + fun `profiler does not start when rate limited`() { + val executorService = DeferredExecutorService() + val profiler = fixture.getSut { + it.executorService = executorService + } + val rateLimiter = mock() + whenever(rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)).thenReturn(true) + whenever(fixture.scopes.rateLimiter).thenReturn(rateLimiter) + + // If the SDK is rate limited, the profiler should never start + profiler.start() + assertFalse(profiler.isRunning) + verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler.")) + } + + @Test + fun `profiler does not start when offline`() { + val executorService = DeferredExecutorService() + val profiler = fixture.getSut { + it.executorService = executorService + it.connectionStatusProvider = mock { provider -> + whenever(provider.connectionStatus).thenReturn(IConnectionStatusProvider.ConnectionStatus.DISCONNECTED) + } + } + + // If the device is offline, the profiler should never start + profiler.start() + assertFalse(profiler.isRunning) + verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("Device is offline. Stopping profiler.")) + } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index 0e061c84c09..ccc352dd031 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -41,7 +41,7 @@ public final class io/sentry/opentelemetry/OtelSpanFactory : io/sentry/ISpanFact public fun ()V public fun (Lio/opentelemetry/api/OpenTelemetry;)V public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan; - public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction; + public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/CompositePerformanceCollector;)Lio/sentry/ITransaction; } public final class io/sentry/opentelemetry/OtelTransactionSpanForwarder : io/sentry/ITransaction { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 7ff8b12167f..7fa2abafa7c 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -341,6 +341,7 @@ public final class io/sentry/DataCategory : java/lang/Enum { public static final field Error Lio/sentry/DataCategory; public static final field Monitor Lio/sentry/DataCategory; public static final field Profile Lio/sentry/DataCategory; + public static final field ProfileChunk Lio/sentry/DataCategory; public static final field Replay Lio/sentry/DataCategory; public static final field Security Lio/sentry/DataCategory; public static final field Session Lio/sentry/DataCategory; diff --git a/sentry/src/main/java/io/sentry/DataCategory.java b/sentry/src/main/java/io/sentry/DataCategory.java index 43181eaeac0..bbee8f2e849 100644 --- a/sentry/src/main/java/io/sentry/DataCategory.java +++ b/sentry/src/main/java/io/sentry/DataCategory.java @@ -12,6 +12,7 @@ public enum DataCategory { Attachment("attachment"), Monitor("monitor"), Profile("profile"), + ProfileChunk("profile_chunk"), Transaction("transaction"), Replay("replay"), Span("span"), diff --git a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java index b4d4574abae..fae019f464b 100644 --- a/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java +++ b/sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java @@ -165,6 +165,9 @@ private DataCategory categoryFromItemType(SentryItemType itemType) { if (SentryItemType.Profile.equals(itemType)) { return DataCategory.Profile; } + if (SentryItemType.ProfileChunk.equals(itemType)) { + return DataCategory.ProfileChunk; + } if (SentryItemType.Attachment.equals(itemType)) { return DataCategory.Attachment; } diff --git a/sentry/src/main/java/io/sentry/transport/RateLimiter.java b/sentry/src/main/java/io/sentry/transport/RateLimiter.java index cb560aff99c..e8ac92e0bc6 100644 --- a/sentry/src/main/java/io/sentry/transport/RateLimiter.java +++ b/sentry/src/main/java/io/sentry/transport/RateLimiter.java @@ -184,6 +184,8 @@ private boolean isRetryAfter(final @NotNull String itemType) { return DataCategory.Attachment; case "profile": return DataCategory.Profile; + case "profile_chunk": + return DataCategory.ProfileChunk; case "transaction": return DataCategory.Transaction; case "check_in": diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index 1b7ae7fe614..9cc24fcabcd 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -10,6 +10,7 @@ import io.sentry.ILogger import io.sentry.IScopes import io.sentry.ISerializer import io.sentry.NoOpLogger +import io.sentry.ProfileChunk import io.sentry.ProfilingTraceData import io.sentry.ReplayRecording import io.sentry.SentryEnvelope @@ -208,8 +209,9 @@ class RateLimiterTest { val attachmentItem = SentryEnvelopeItem.fromAttachment(fixture.serializer, NoOpLogger.getInstance(), Attachment("{ \"number\": 10 }".toByteArray(), "log.json"), 1000) val profileItem = SentryEnvelopeItem.fromProfilingTrace(ProfilingTraceData(File(""), transaction), 1000, fixture.serializer) val checkInItem = SentryEnvelopeItem.fromCheckIn(fixture.serializer, CheckIn("monitor-slug-1", CheckInStatus.ERROR)) + val profileChunkItem = SentryEnvelopeItem.fromProfileChunk(ProfileChunk(), fixture.serializer) - val envelope = SentryEnvelope(SentryEnvelopeHeader(), arrayListOf(eventItem, userFeedbackItem, sessionItem, attachmentItem, profileItem, checkInItem)) + val envelope = SentryEnvelope(SentryEnvelopeHeader(), arrayListOf(eventItem, userFeedbackItem, sessionItem, attachmentItem, profileItem, checkInItem, profileChunkItem)) rateLimiter.updateRetryAfterLimits(null, null, 429) val result = rateLimiter.filter(envelope, Hint()) @@ -222,6 +224,7 @@ class RateLimiterTest { verify(fixture.clientReportRecorder, times(1)).recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(attachmentItem)) verify(fixture.clientReportRecorder, times(1)).recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(profileItem)) verify(fixture.clientReportRecorder, times(1)).recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(checkInItem)) + verify(fixture.clientReportRecorder, times(1)).recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(profileChunkItem)) verifyNoMoreInteractions(fixture.clientReportRecorder) } @@ -331,6 +334,24 @@ class RateLimiterTest { verifyNoMoreInteractions(fixture.clientReportRecorder) } + @Test + fun `drop profileChunk items as lost`() { + val rateLimiter = fixture.getSUT() + + val profileChunkItem = SentryEnvelopeItem.fromProfileChunk(ProfileChunk(), fixture.serializer) + val attachmentItem = SentryEnvelopeItem.fromAttachment(fixture.serializer, NoOpLogger.getInstance(), Attachment("{ \"number\": 10 }".toByteArray(), "log.json"), 1000) + val envelope = SentryEnvelope(SentryEnvelopeHeader(), arrayListOf(profileChunkItem, attachmentItem)) + + rateLimiter.updateRetryAfterLimits("60:profile_chunk:key", null, 1) + val result = rateLimiter.filter(envelope, Hint()) + + assertNotNull(result) + assertEquals(1, result.items.toList().size) + + verify(fixture.clientReportRecorder, times(1)).recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(profileChunkItem)) + verifyNoMoreInteractions(fixture.clientReportRecorder) + } + @Test fun `apply rate limits notifies observers`() { val rateLimiter = fixture.getSUT() From 24f195f2bfb143093341aba422c1ba447313eb69 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 25 Nov 2024 11:33:24 +0100 Subject: [PATCH 2/4] continuous profiler now reset its id when rate limited or offline --- .../io/sentry/android/core/AndroidContinuousProfiler.java | 7 +++++++ .../sentry/android/core/AndroidContinuousProfilerTest.kt | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index c4d2e09c08c..fb8d465f64e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -125,12 +125,16 @@ public synchronized void start() { && (rateLimiter.isActiveForCategory(All) || rateLimiter.isActiveForCategory(DataCategory.ProfileChunk))) { logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler."); + // Let's stop and reset profiler id, as the profile is now broken anyway + stop(); return; } // If device is offline, we don't start the profiler, to avoid flooding the cache if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) { logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler."); + // Let's stop and reset profiler id, as the profile is now broken anyway + stop(); return; } } @@ -176,6 +180,9 @@ private synchronized void stop(final boolean restartProfiler) { } // check if profiler was created and it's running if (profiler == null || !isRunning) { + // When the profiler is stopped due to an error (e.g. offline or rate limited), reset the ids + profilerId = SentryId.EMPTY_ID; + chunkId = SentryId.EMPTY_ID; return; } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt index 3fa6bd7da2e..8093b2ee8b2 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidContinuousProfilerTest.kt @@ -20,6 +20,7 @@ import io.sentry.SentryTracer import io.sentry.TransactionContext import io.sentry.android.core.internal.util.SentryFrameMetricsCollector import io.sentry.profilemeasurements.ProfileMeasurement +import io.sentry.protocol.SentryId import io.sentry.test.DeferredExecutorService import io.sentry.test.getProperty import io.sentry.transport.RateLimiter @@ -40,6 +41,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertContains +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -430,6 +432,7 @@ class AndroidContinuousProfilerTest { // If the SDK is rate limited, the profiler should stop profiler.onRateLimitChanged(rateLimiter) assertFalse(profiler.isRunning) + assertEquals(SentryId.EMPTY_ID, profiler.profilerId) verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler.")) } @@ -446,6 +449,7 @@ class AndroidContinuousProfilerTest { // If the SDK is rate limited, the profiler should never start profiler.start() assertFalse(profiler.isRunning) + assertEquals(SentryId.EMPTY_ID, profiler.profilerId) verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler.")) } @@ -462,6 +466,7 @@ class AndroidContinuousProfilerTest { // If the device is offline, the profiler should never start profiler.start() assertFalse(profiler.isRunning) + assertEquals(SentryId.EMPTY_ID, profiler.profilerId) verify(fixture.mockLogger).log(eq(SentryLevel.WARNING), eq("Device is offline. Stopping profiler.")) } } From 9da1261637ebbd6d8559d4b18b1c1dd218fbf8de Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 6 Dec 2024 16:24:16 +0100 Subject: [PATCH 3/4] changed AndroidContinuousProfiler.isClosed to AtomicBoolean --- .../io/sentry/android/core/AndroidContinuousProfiler.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index fb8d465f64e..2c9df5e5797 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -50,7 +52,7 @@ public class AndroidContinuousProfiler private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; - private boolean isClosed = false; + private @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); public AndroidContinuousProfiler( final @NotNull BuildInfoProvider buildInfoProvider, @@ -236,7 +238,7 @@ private synchronized void stop(final boolean restartProfiler) { public synchronized void close() { stop(); - isClosed = true; + isClosed.set(true); } @Override @@ -251,7 +253,7 @@ private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOpti .submit( () -> { // SDK is closed, we don't send the chunks - if (isClosed) { + if (isClosed.get()) { return; } final ArrayList payloads = new ArrayList<>(payloadBuilders.size()); From 14ba4688a6a06516269de8406a16b812dcd90733 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 6 Dec 2024 16:27:31 +0100 Subject: [PATCH 4/4] changed AndroidContinuousProfiler.isClosed to AtomicBoolean --- .../java/io/sentry/android/core/AndroidContinuousProfiler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java index 2c9df5e5797..99c042e015b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidContinuousProfiler.java @@ -26,7 +26,6 @@ import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,7 +51,7 @@ public class AndroidContinuousProfiler private final @NotNull List payloadBuilders = new ArrayList<>(); private @NotNull SentryId profilerId = SentryId.EMPTY_ID; private @NotNull SentryId chunkId = SentryId.EMPTY_ID; - private @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); + private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); public AndroidContinuousProfiler( final @NotNull BuildInfoProvider buildInfoProvider,