4141 * profiling backends independent. All ProfilingManager API usage is confined to this file and {@link
4242 * PerfettoProfiler}.
4343 *
44- * <p>Unlike the legacy profiler, this class is not used for app-start profiling. It is created
45- * during {@code Sentry.init()}, so scopes are always available when {@link #startProfiler} is
46- * called.
44+ * <p>Currently, this class doesn't do app-start profiling {@link SentryPerformanceProvider}.
45+ * It is created during {@code Sentry.init()}.
46+ *
47+ * <p>Thread safety: all mutable state is guarded by a single {@link
48+ * io.sentry.util.AutoClosableReentrantLock}. Public entry points ({@link #startProfiler}, {@link
49+ * #stopProfiler}, {@link #close}, {@link #onRateLimitChanged}, {@link #reevaluateSampling}, and
50+ * the getters) acquire the lock themselves and are thread-safe.
51+ * Private methods {@code startInternal} and {@code stopInternal} require the caller to hold the lock.
4752 */
4853@ ApiStatus .Internal
4954@ RequiresApi (api = Build .VERSION_CODES .VANILLA_ICE_CREAM )
5055public class PerfettoContinuousProfiler
5156 implements IContinuousProfiler , RateLimiter .IRateLimitObserver {
52-
5357 private static final long MAX_CHUNK_DURATION_MILLIS = 60000 ;
5458
5559 private final @ NotNull ILogger logger ;
@@ -67,7 +71,7 @@ public class PerfettoContinuousProfiler
6771 private final @ NotNull AtomicBoolean isClosed = new AtomicBoolean (false );
6872 private @ NotNull SentryDate startProfileChunkTimestamp =
6973 new io .sentry .SentryNanotimeDate ();
70- private volatile boolean shouldSample = true ;
74+ private boolean shouldSample = true ;
7175 private boolean shouldStop = false ;
7276 private boolean isSampled = false ;
7377 private int activeTraceCount = 0 ;
@@ -141,10 +145,9 @@ public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
141145 }
142146
143147 /**
144- * Stop the profiler as soon as we are rate limited, to avoid the performance overhead
148+ * Stop the profiler as soon as we are rate limited, to avoid the performance overhead.
145149 *
146- * @param rateLimiter this {@link RateLimiter} instance which you can use to check if the rate
147- * limit is active for a specific category
150+ * @param rateLimiter the {@link RateLimiter} instance to check categories against
148151 */
149152 @ Override
150153 public void onRateLimitChanged (@ NotNull RateLimiter rateLimiter ) {
@@ -173,23 +176,31 @@ public void close(final boolean isTerminating) {
173176
174177 @ Override
175178 public @ NotNull SentryId getProfilerId () {
176- return profilerId ;
179+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
180+ return profilerId ;
181+ }
177182 }
178183
179184 @ Override
180185 public @ NotNull SentryId getChunkId () {
181- return chunkId ;
186+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
187+ return chunkId ;
188+ }
182189 }
183190
184191 @ Override
185192 public boolean isRunning () {
186- return isRunning ;
193+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
194+ return isRunning ;
195+ }
187196 }
188197
189198 /**
190199 * Resolves scopes on first call. Since PerfettoContinuousProfiler is created during
191200 * Sentry.init() and never used for app-start profiling, scopes is guaranteed to be available by
192201 * the time startProfiler is called.
202+ *
203+ * <p>Caller must hold {@link #lock}.
193204 */
194205 private @ NotNull IScopes resolveScopes () {
195206 if (scopes != null && scopes != NoOpScopes .getInstance ()) {
@@ -345,7 +356,9 @@ private void ensureProfiler() {
345356 }
346357
347358 public void reevaluateSampling () {
348- shouldSample = true ;
359+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
360+ shouldSample = true ;
361+ }
349362 }
350363
351364 private void sendChunk (
0 commit comments