From 64a61fe63adc1881eebf3e10917f1b8e3f6d028b Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 3 Oct 2024 14:26:20 +0200 Subject: [PATCH 01/11] Make AtomicClientReportStorage constructor lazy --- .../AtomicClientReportStorage.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java b/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java index 2e7f0c27a7..eff2220553 100644 --- a/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java +++ b/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java @@ -1,10 +1,12 @@ package io.sentry.clientreport; import io.sentry.DataCategory; +import io.sentry.util.LazyEvaluator; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.jetbrains.annotations.ApiStatus; @@ -14,25 +16,25 @@ @ApiStatus.Internal final class AtomicClientReportStorage implements IClientReportStorage { - private final @NotNull Map lostEventCounts; - - public AtomicClientReportStorage() { + private final @NotNull LazyEvaluator> lostEventCounts = new LazyEvaluator<>(() -> { final Map modifyableEventCountsForInit = new ConcurrentHashMap<>(); for (final DiscardReason discardReason : DiscardReason.values()) { for (final DataCategory category : DataCategory.values()) { modifyableEventCountsForInit.put( - new ClientReportKey(discardReason.getReason(), category.getCategory()), - new AtomicLong(0)); + new ClientReportKey(discardReason.getReason(), category.getCategory()), + new AtomicLong(0)); } } - lostEventCounts = Collections.unmodifiableMap(modifyableEventCountsForInit); - } + return Collections.unmodifiableMap(modifyableEventCountsForInit); + }); + + public AtomicClientReportStorage() {} @Override public void addCount(ClientReportKey key, Long count) { - final @Nullable AtomicLong quantity = lostEventCounts.get(key); + final @Nullable AtomicLong quantity = lostEventCounts.getValue().get(key); if (quantity != null) { quantity.addAndGet(count); @@ -43,7 +45,8 @@ public void addCount(ClientReportKey key, Long count) { public List resetCountsAndGet() { final List discardedEvents = new ArrayList<>(); - for (final Map.Entry entry : lostEventCounts.entrySet()) { + Set> entrySet = lostEventCounts.getValue().entrySet(); + for (final Map.Entry entry : entrySet) { final Long quantity = entry.getValue().getAndSet(0); if (quantity > 0) { discardedEvents.add( From 4210b0b815bf912c9219e762c97bb8b2627eb055 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 3 Oct 2024 15:11:05 +0200 Subject: [PATCH 02/11] Lazily initialize things we dont need --- .../main/java/io/sentry/SentryOptions.java | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 3ff84c48de..9265955183 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -19,13 +19,13 @@ import io.sentry.transport.ITransportGate; import io.sentry.transport.NoOpEnvelopeCache; import io.sentry.transport.NoOpTransportGate; +import io.sentry.util.Objects; import io.sentry.util.Platform; import io.sentry.util.SampleRateUtils; import io.sentry.util.StringUtils; import io.sentry.util.thread.IMainThreadChecker; import io.sentry.util.thread.NoOpMainThreadChecker; import java.io.File; -import java.net.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -119,10 +119,14 @@ public class SentryOptions { private @NotNull SentryLevel diagnosticLevel = DEFAULT_DIAGNOSTIC_LEVEL; /** Envelope reader interface */ - private @NotNull IEnvelopeReader envelopeReader = new EnvelopeReader(new JsonSerializer(this)); + private volatile @Nullable IEnvelopeReader envelopeReader; + + private final @NotNull Object envelopeReaderLock = new Object(); /** Serializer interface to serialize/deserialize json events */ - private @NotNull ISerializer serializer = new JsonSerializer(this); + private volatile @Nullable ISerializer serializer; + + private final @NotNull Object serializerLock = new Object(); /** Max depth when serializing object graphs with reflection. * */ private int maxDepth = 100; @@ -416,7 +420,9 @@ public class SentryOptions { /** Date provider to retrieve the current date from. */ @ApiStatus.Internal - private @NotNull SentryDateProvider dateProvider = new SentryAutoDateProvider(); + private volatile @Nullable SentryDateProvider dateProvider; + + private final @NotNull Object dateProviderLock = new Object(); private final @NotNull List performanceCollectors = new ArrayList<>(); @@ -605,7 +611,14 @@ public void setDiagnosticLevel(@Nullable final SentryLevel diagnosticLevel) { * @return the serializer */ public @NotNull ISerializer getSerializer() { - return serializer; + if (serializer == null) { + synchronized (serializerLock) { + if (serializer == null) { + serializer = new JsonSerializer(this); + } + } + } + return Objects.requireNonNull(serializer, "Serializer was null"); } /** @@ -636,7 +649,14 @@ public void setMaxDepth(int maxDepth) { } public @NotNull IEnvelopeReader getEnvelopeReader() { - return envelopeReader; + if (envelopeReader == null) { + synchronized (envelopeReaderLock) { + if (envelopeReader == null) { + envelopeReader = new EnvelopeReader(getSerializer()); + } + } + } + return Objects.requireNonNull(envelopeReader, "EnvelopeReader was null"); } public void setEnvelopeReader(final @Nullable IEnvelopeReader envelopeReader) { @@ -2212,7 +2232,14 @@ public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { /** Returns the current {@link SentryDateProvider} that is used to retrieve the current date. */ @ApiStatus.Internal public @NotNull SentryDateProvider getDateProvider() { - return dateProvider; + if (dateProvider == null) { + synchronized (dateProviderLock) { + if (dateProvider == null) { + dateProvider = new SentryAutoDateProvider(); + } + } + } + return Objects.requireNonNull(dateProvider, "DateProvider is not set"); } /** From 79e8ce1db5624bd113301cfcdd28b6c71e89dce9 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Thu, 3 Oct 2024 15:51:21 +0200 Subject: [PATCH 03/11] Empty experimental options --- .../src/main/java/io/sentry/ExperimentalOptions.java | 6 +++++- sentry/src/main/java/io/sentry/SentryOptions.java | 3 ++- .../src/main/java/io/sentry/SentryReplayOptions.java | 10 ++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/sentry/src/main/java/io/sentry/ExperimentalOptions.java b/sentry/src/main/java/io/sentry/ExperimentalOptions.java index f587996bd8..4a0e7de78d 100644 --- a/sentry/src/main/java/io/sentry/ExperimentalOptions.java +++ b/sentry/src/main/java/io/sentry/ExperimentalOptions.java @@ -9,7 +9,11 @@ *

Beware that experimental options can change at any time. */ public final class ExperimentalOptions { - private @NotNull SentryReplayOptions sessionReplay = new SentryReplayOptions(); + private @NotNull SentryReplayOptions sessionReplay; + + public ExperimentalOptions(final boolean empty) { + this.sessionReplay = new SentryReplayOptions(empty); + } @NotNull public SentryReplayOptions getSessionReplay() { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 9265955183..5f962b62bf 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -485,7 +485,7 @@ public class SentryOptions { @ApiStatus.Experimental private @Nullable Cron cron = null; - private final @NotNull ExperimentalOptions experimental = new ExperimentalOptions(); + private final @NotNull ExperimentalOptions experimental; private @NotNull ReplayController replayController = NoOpReplayController.getInstance(); @@ -2567,6 +2567,7 @@ public SentryOptions() { * @param empty if options should be empty. */ private SentryOptions(final boolean empty) { + experimental = new ExperimentalOptions(empty); if (!empty) { // SentryExecutorService should be initialized before any // SendCachedEventFireAndForgetIntegration diff --git a/sentry/src/main/java/io/sentry/SentryReplayOptions.java b/sentry/src/main/java/io/sentry/SentryReplayOptions.java index 7656b088a1..097f72c921 100644 --- a/sentry/src/main/java/io/sentry/SentryReplayOptions.java +++ b/sentry/src/main/java/io/sentry/SentryReplayOptions.java @@ -96,14 +96,16 @@ public enum SentryReplayQuality { /** The maximum duration of a full session replay, defaults to 1h. */ private long sessionDuration = 60 * 60 * 1000L; - public SentryReplayOptions() { - setRedactAllText(true); - setRedactAllImages(true); + public SentryReplayOptions(final boolean empty) { + if (!empty) { + setRedactAllText(true); + setRedactAllImages(true); + } } public SentryReplayOptions( final @Nullable Double sessionSampleRate, final @Nullable Double onErrorSampleRate) { - this(); + this(false); this.sessionSampleRate = sessionSampleRate; this.onErrorSampleRate = onErrorSampleRate; } From 0511933f2508c5ce86f226bcac8d99eefdf42c19 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 3 Oct 2024 14:29:47 +0000 Subject: [PATCH 04/11] Format code --- .../main/java/io/sentry/SentryOptions.java | 3 +- .../AtomicClientReportStorage.java | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 5f962b62bf..6ad6ed3dba 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -419,8 +419,7 @@ public class SentryOptions { private boolean traceOptionsRequests = true; /** Date provider to retrieve the current date from. */ - @ApiStatus.Internal - private volatile @Nullable SentryDateProvider dateProvider; + @ApiStatus.Internal private volatile @Nullable SentryDateProvider dateProvider; private final @NotNull Object dateProviderLock = new Object(); diff --git a/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java b/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java index eff2220553..fd1f9a9de9 100644 --- a/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java +++ b/sentry/src/main/java/io/sentry/clientreport/AtomicClientReportStorage.java @@ -16,19 +16,22 @@ @ApiStatus.Internal final class AtomicClientReportStorage implements IClientReportStorage { - private final @NotNull LazyEvaluator> lostEventCounts = new LazyEvaluator<>(() -> { - final Map modifyableEventCountsForInit = new ConcurrentHashMap<>(); - - for (final DiscardReason discardReason : DiscardReason.values()) { - for (final DataCategory category : DataCategory.values()) { - modifyableEventCountsForInit.put( - new ClientReportKey(discardReason.getReason(), category.getCategory()), - new AtomicLong(0)); - } - } - - return Collections.unmodifiableMap(modifyableEventCountsForInit); - }); + private final @NotNull LazyEvaluator> lostEventCounts = + new LazyEvaluator<>( + () -> { + final Map modifyableEventCountsForInit = + new ConcurrentHashMap<>(); + + for (final DiscardReason discardReason : DiscardReason.values()) { + for (final DataCategory category : DataCategory.values()) { + modifyableEventCountsForInit.put( + new ClientReportKey(discardReason.getReason(), category.getCategory()), + new AtomicLong(0)); + } + } + + return Collections.unmodifiableMap(modifyableEventCountsForInit); + }); public AtomicClientReportStorage() {} From c8ea8098790f1b767a904d34adf7dcb95f8fde37 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 8 Oct 2024 09:07:34 +0200 Subject: [PATCH 05/11] Make LazyEvaluator thread-safe --- .../src/main/java/io/sentry/util/LazyEvaluator.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java index 5db376e710..7022108f90 100644 --- a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java +++ b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java @@ -10,7 +10,8 @@ */ @ApiStatus.Internal public final class LazyEvaluator { - private @Nullable T value = null; + + private volatile @Nullable T value = null; private final @NotNull Evaluator evaluator; /** @@ -28,10 +29,16 @@ public LazyEvaluator(final @NotNull Evaluator evaluator) { * * @return The result of the evaluator function. */ - public synchronized @NotNull T getValue() { + public @NotNull T getValue() { if (value == null) { - value = evaluator.evaluate(); + synchronized (this) { + if (value == null) { + value = evaluator.evaluate(); + } + } } + + //noinspection DataFlowIssue return value; } From 02fd16fd95e4c845eb7f6d8ada842201065c5cd0 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 8 Oct 2024 09:07:40 +0200 Subject: [PATCH 06/11] Fix tests --- sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt index 01843dfc90..794a3dac09 100644 --- a/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryReplayOptionsTest.kt @@ -7,7 +7,7 @@ class SentryReplayOptionsTest { @Test fun `uses medium quality as default`() { - val replayOptions = SentryReplayOptions() + val replayOptions = SentryReplayOptions(true) assertEquals(SentryReplayOptions.SentryReplayQuality.MEDIUM, replayOptions.quality) assertEquals(75_000, replayOptions.quality.bitRate) @@ -16,7 +16,7 @@ class SentryReplayOptionsTest { @Test fun `low quality`() { - val replayOptions = SentryReplayOptions().apply { quality = SentryReplayOptions.SentryReplayQuality.LOW } + val replayOptions = SentryReplayOptions(true).apply { quality = SentryReplayOptions.SentryReplayQuality.LOW } assertEquals(50_000, replayOptions.quality.bitRate) assertEquals(0.8f, replayOptions.quality.sizeScale) @@ -24,7 +24,7 @@ class SentryReplayOptionsTest { @Test fun `high quality`() { - val replayOptions = SentryReplayOptions().apply { quality = SentryReplayOptions.SentryReplayQuality.HIGH } + val replayOptions = SentryReplayOptions(true).apply { quality = SentryReplayOptions.SentryReplayQuality.HIGH } assertEquals(100_000, replayOptions.quality.bitRate) assertEquals(1.0f, replayOptions.quality.sizeScale) From 5ed2cfe4413d3fa6e00c6dc790bdf044f2aed49c Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 8 Oct 2024 09:27:21 +0200 Subject: [PATCH 07/11] Fix .api --- sentry/api/sentry.api | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 530d8241f5..6bc521180a 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -315,7 +315,7 @@ public abstract interface class io/sentry/EventProcessor { } public final class io/sentry/ExperimentalOptions { - public fun ()V + public fun (Z)V public fun getSessionReplay ()Lio/sentry/SentryReplayOptions; public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V } @@ -2712,8 +2712,8 @@ public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sent public final class io/sentry/SentryReplayOptions { public static final field IMAGE_VIEW_CLASS_NAME Ljava/lang/String; public static final field TEXT_VIEW_CLASS_NAME Ljava/lang/String; - public fun ()V public fun (Ljava/lang/Double;Ljava/lang/Double;)V + public fun (Z)V public fun addIgnoreViewClass (Ljava/lang/String;)V public fun addRedactViewClass (Ljava/lang/String;)V public fun getErrorReplayDuration ()J From b6f9dd05c42dc0915fe5ed593b4545526ee43ebf Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 8 Oct 2024 16:20:35 +0200 Subject: [PATCH 08/11] Use LazyEvaluator for heavy SentryOptions --- sentry/api/sentry.api | 1 + .../main/java/io/sentry/SentryOptions.java | 55 ++++++------------- .../java/io/sentry/cache/CacheStrategy.java | 15 ++--- .../java/io/sentry/cache/EnvelopeCache.java | 12 ++-- .../java/io/sentry/util/LazyEvaluator.java | 6 ++ 5 files changed, 37 insertions(+), 52 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 6bc521180a..059262d2c3 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5698,6 +5698,7 @@ public final class io/sentry/util/JsonSerializationUtils { public final class io/sentry/util/LazyEvaluator { public fun (Lio/sentry/util/LazyEvaluator$Evaluator;)V public fun getValue ()Ljava/lang/Object; + public fun setValue (Ljava/lang/Object;)V } public abstract interface class io/sentry/util/LazyEvaluator$Evaluator { diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 6ad6ed3dba..61c721847c 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -19,7 +19,7 @@ import io.sentry.transport.ITransportGate; import io.sentry.transport.NoOpEnvelopeCache; import io.sentry.transport.NoOpTransportGate; -import io.sentry.util.Objects; +import io.sentry.util.LazyEvaluator; import io.sentry.util.Platform; import io.sentry.util.SampleRateUtils; import io.sentry.util.StringUtils; @@ -118,15 +118,13 @@ public class SentryOptions { /** minimum LogLevel to be used if debug is enabled */ private @NotNull SentryLevel diagnosticLevel = DEFAULT_DIAGNOSTIC_LEVEL; - /** Envelope reader interface */ - private volatile @Nullable IEnvelopeReader envelopeReader; - - private final @NotNull Object envelopeReaderLock = new Object(); - /** Serializer interface to serialize/deserialize json events */ - private volatile @Nullable ISerializer serializer; + private final @NotNull LazyEvaluator serializer = + new LazyEvaluator<>(() -> new JsonSerializer(this)); - private final @NotNull Object serializerLock = new Object(); + /** Envelope reader interface */ + private final @NotNull LazyEvaluator envelopeReader = + new LazyEvaluator<>(() -> new EnvelopeReader(serializer.getValue())); /** Max depth when serializing object graphs with reflection. * */ private int maxDepth = 100; @@ -419,9 +417,9 @@ public class SentryOptions { private boolean traceOptionsRequests = true; /** Date provider to retrieve the current date from. */ - @ApiStatus.Internal private volatile @Nullable SentryDateProvider dateProvider; - - private final @NotNull Object dateProviderLock = new Object(); + @ApiStatus.Internal + private final @NotNull LazyEvaluator dateProvider = + new LazyEvaluator<>(() -> new SentryAutoDateProvider()); private final @NotNull List performanceCollectors = new ArrayList<>(); @@ -610,14 +608,7 @@ public void setDiagnosticLevel(@Nullable final SentryLevel diagnosticLevel) { * @return the serializer */ public @NotNull ISerializer getSerializer() { - if (serializer == null) { - synchronized (serializerLock) { - if (serializer == null) { - serializer = new JsonSerializer(this); - } - } - } - return Objects.requireNonNull(serializer, "Serializer was null"); + return serializer.getValue(); } /** @@ -626,7 +617,7 @@ public void setDiagnosticLevel(@Nullable final SentryLevel diagnosticLevel) { * @param serializer the serializer */ public void setSerializer(@Nullable ISerializer serializer) { - this.serializer = serializer != null ? serializer : NoOpSerializer.getInstance(); + this.serializer.setValue(serializer != null ? serializer : NoOpSerializer.getInstance()); } /** @@ -648,19 +639,12 @@ public void setMaxDepth(int maxDepth) { } public @NotNull IEnvelopeReader getEnvelopeReader() { - if (envelopeReader == null) { - synchronized (envelopeReaderLock) { - if (envelopeReader == null) { - envelopeReader = new EnvelopeReader(getSerializer()); - } - } - } - return Objects.requireNonNull(envelopeReader, "EnvelopeReader was null"); + return envelopeReader.getValue(); } public void setEnvelopeReader(final @Nullable IEnvelopeReader envelopeReader) { - this.envelopeReader = - envelopeReader != null ? envelopeReader : NoOpEnvelopeReader.getInstance(); + this.envelopeReader.setValue( + envelopeReader != null ? envelopeReader : NoOpEnvelopeReader.getInstance()); } /** @@ -2231,14 +2215,7 @@ public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { /** Returns the current {@link SentryDateProvider} that is used to retrieve the current date. */ @ApiStatus.Internal public @NotNull SentryDateProvider getDateProvider() { - if (dateProvider == null) { - synchronized (dateProviderLock) { - if (dateProvider == null) { - dateProvider = new SentryAutoDateProvider(); - } - } - } - return Objects.requireNonNull(dateProvider, "DateProvider is not set"); + return dateProvider.getValue(); } /** @@ -2249,7 +2226,7 @@ public void setIgnoredCheckIns(final @Nullable List ignoredCheckIns) { */ @ApiStatus.Internal public void setDateProvider(final @NotNull SentryDateProvider dateProvider) { - this.dateProvider = dateProvider; + this.dateProvider.setValue(dateProvider); } /** diff --git a/sentry/src/main/java/io/sentry/cache/CacheStrategy.java b/sentry/src/main/java/io/sentry/cache/CacheStrategy.java index dbb6a49c19..d48cc3108d 100644 --- a/sentry/src/main/java/io/sentry/cache/CacheStrategy.java +++ b/sentry/src/main/java/io/sentry/cache/CacheStrategy.java @@ -10,6 +10,7 @@ import io.sentry.SentryOptions; import io.sentry.Session; import io.sentry.clientreport.DiscardReason; +import io.sentry.util.LazyEvaluator; import io.sentry.util.Objects; import java.io.BufferedInputStream; import java.io.BufferedReader; @@ -36,8 +37,9 @@ abstract class CacheStrategy { @SuppressWarnings("CharsetObjectCanBeUsed") protected static final Charset UTF_8 = Charset.forName("UTF-8"); - protected final @NotNull SentryOptions options; - protected final @NotNull ISerializer serializer; + protected @NotNull SentryOptions options; + protected final @NotNull LazyEvaluator serializer = + new LazyEvaluator<>(() -> options.getSerializer()); protected final @NotNull File directory; private final int maxSize; @@ -48,7 +50,6 @@ abstract class CacheStrategy { Objects.requireNonNull(directoryPath, "Directory is required."); this.options = Objects.requireNonNull(options, "SentryOptions is required."); - this.serializer = options.getSerializer(); this.directory = new File(directoryPath); this.maxSize = maxSize; @@ -177,7 +178,7 @@ private void moveInitFlagIfNecessary( && currentSession.getSessionId().equals(session.getSessionId())) { session.setInitAsTrue(); try { - newSessionItem = SentryEnvelopeItem.fromSession(serializer, session); + newSessionItem = SentryEnvelopeItem.fromSession(serializer.getValue(), session); // remove item from envelope items so we can replace with the new one that has the // init flag true itemsIterator.remove(); @@ -216,7 +217,7 @@ private void moveInitFlagIfNecessary( private @Nullable SentryEnvelope readEnvelope(final @NotNull File file) { try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { - return serializer.deserializeEnvelope(inputStream); + return serializer.getValue().deserializeEnvelope(inputStream); } catch (IOException e) { options.getLogger().log(ERROR, "Failed to deserialize the envelope.", e); } @@ -258,7 +259,7 @@ private boolean isSessionType(final @Nullable SentryEnvelopeItem item) { try (final Reader reader = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) { - return serializer.deserialize(reader, Session.class); + return serializer.getValue().deserialize(reader, Session.class); } catch (Throwable e) { options.getLogger().log(ERROR, "Failed to deserialize the session.", e); } @@ -268,7 +269,7 @@ private boolean isSessionType(final @Nullable SentryEnvelopeItem item) { private void saveNewEnvelope( final @NotNull SentryEnvelope envelope, final @NotNull File file, final long timestamp) { try (final OutputStream outputStream = new FileOutputStream(file)) { - serializer.serialize(envelope, outputStream); + serializer.getValue().serialize(envelope, outputStream); // we need to set the same timestamp so the sorting from oldest to newest wont break. file.setLastModified(timestamp); } catch (Throwable e) { diff --git a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java index 3be857a4b2..82636ac6c1 100644 --- a/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java +++ b/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java @@ -116,7 +116,7 @@ public void store(final @NotNull SentryEnvelope envelope, final @NotNull Hint hi try (final Reader reader = new BufferedReader( new InputStreamReader(new FileInputStream(currentSessionFile), UTF_8))) { - final Session session = serializer.deserialize(reader, Session.class); + final Session session = serializer.getValue().deserialize(reader, Session.class); if (session != null) { writeSessionToDisk(previousSessionFile, session); } @@ -204,7 +204,7 @@ private void tryEndPreviousSession(final @NotNull Hint hint) { try (final Reader reader = new BufferedReader( new InputStreamReader(new FileInputStream(previousSessionFile), UTF_8))) { - final Session session = serializer.deserialize(reader, Session.class); + final Session session = serializer.getValue().deserialize(reader, Session.class); if (session != null) { final AbnormalExit abnormalHint = (AbnormalExit) sdkHint; final @Nullable Long abnormalExitTimestamp = abnormalHint.timestamp(); @@ -263,7 +263,7 @@ private void updateCurrentSession( try (final Reader reader = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(item.getData()), UTF_8))) { - final Session session = serializer.deserialize(reader, Session.class); + final Session session = serializer.getValue().deserialize(reader, Session.class); if (session == null) { options .getLogger() @@ -304,7 +304,7 @@ private void writeEnvelopeToDisk( } try (final OutputStream outputStream = new FileOutputStream(file)) { - serializer.serialize(envelope, outputStream); + serializer.getValue().serialize(envelope, outputStream); } catch (Throwable e) { options .getLogger() @@ -324,7 +324,7 @@ private void writeSessionToDisk(final @NotNull File file, final @NotNull Session try (final OutputStream outputStream = new FileOutputStream(file); final Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) { - serializer.serialize(session, writer); + serializer.getValue().serialize(session, writer); } catch (Throwable e) { options .getLogger() @@ -388,7 +388,7 @@ public void discard(final @NotNull SentryEnvelope envelope) { for (final File file : allCachedEnvelopes) { try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) { - ret.add(serializer.deserializeEnvelope(is)); + ret.add(serializer.getValue().deserializeEnvelope(is)); } catch (FileNotFoundException e) { options .getLogger() diff --git a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java index 7022108f90..d540cbe508 100644 --- a/sentry/src/main/java/io/sentry/util/LazyEvaluator.java +++ b/sentry/src/main/java/io/sentry/util/LazyEvaluator.java @@ -42,6 +42,12 @@ public LazyEvaluator(final @NotNull Evaluator evaluator) { return value; } + public void setValue(final @Nullable T value) { + synchronized (this) { + this.value = value; + } + } + public interface Evaluator { @NotNull T evaluate(); From 306add9f6d799253aceb7074fbb7891d6d3ea81c Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 8 Oct 2024 16:22:40 +0200 Subject: [PATCH 09/11] Changelog --- CHANGELOG.md | 1 + sentry-samples/sentry-samples-android/build.gradle.kts | 2 +- .../sentry-samples-android/src/main/AndroidManifest.xml | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e6bc7c535..0cbed67dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified - Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669)) - Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736)) +- Lazily initialize heavy `SentryOptions` members to avoid ANRs on app start ([#3749](https://github.com/getsentry/sentry-java/pull/3749)) *Breaking changes*: diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index a8d8897519..2c94dbba9d 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -133,5 +133,5 @@ dependencies { implementation(Config.Libs.composeNavigation) implementation(Config.Libs.composeMaterial) - debugImplementation(Config.Libs.leakCanary) +// debugImplementation(Config.Libs.leakCanary) } diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 8876efd66d..fd12d5454b 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -73,7 +73,7 @@ - + @@ -168,5 +168,6 @@ + From d437823fbc44e341ca38ac2badb9d9ce7ebd4717 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 8 Oct 2024 16:28:17 +0200 Subject: [PATCH 10/11] revert --- sentry-samples/sentry-samples-android/build.gradle.kts | 2 +- .../sentry-samples-android/src/main/AndroidManifest.xml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sentry-samples/sentry-samples-android/build.gradle.kts b/sentry-samples/sentry-samples-android/build.gradle.kts index 2c94dbba9d..a8d8897519 100644 --- a/sentry-samples/sentry-samples-android/build.gradle.kts +++ b/sentry-samples/sentry-samples-android/build.gradle.kts @@ -133,5 +133,5 @@ dependencies { implementation(Config.Libs.composeNavigation) implementation(Config.Libs.composeMaterial) -// debugImplementation(Config.Libs.leakCanary) + debugImplementation(Config.Libs.leakCanary) } diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index fd12d5454b..997fa5ff55 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -73,7 +73,7 @@ - + @@ -167,7 +167,5 @@ - - From 62f4168108d9d78d573464cd1125c025da945809 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 8 Oct 2024 17:14:36 +0200 Subject: [PATCH 11/11] tests --- .../sentry/android/replay/ScreenshotRecorder.kt | 4 ++++ .../java/io/sentry/cache/CacheStrategyTest.kt | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt index fdab9f442d..0ea3fad6ab 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt @@ -13,6 +13,7 @@ import android.graphics.Rect import android.graphics.RectF import android.os.Build.VERSION import android.os.Build.VERSION_CODES +import android.util.Log import android.view.PixelCopy import android.view.View import android.view.ViewGroup @@ -101,6 +102,7 @@ internal class ScreenshotRecorder( Bitmap.Config.ARGB_8888 ) + val timeStart = System.nanoTime() // postAtFrontOfQueue to ensure the view hierarchy and bitmap are ase close in-sync as possible mainLooperHandler.post { try { @@ -123,6 +125,8 @@ internal class ScreenshotRecorder( val viewHierarchy = ViewHierarchyNode.fromView(root, null, 0, options) root.traverse(viewHierarchy) + val timeEnd = System.nanoTime() + Log.e("TIME", String.format("%.2f", ((timeEnd - timeStart) / 1_000_000.0))) recorder.submitSafely(options, "screenshot_recorder.redact") { val canvas = Canvas(bitmap) diff --git a/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt b/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt index e67a6616ce..3c3e6d18d0 100644 --- a/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt +++ b/sentry/src/test/java/io/sentry/cache/CacheStrategyTest.kt @@ -108,13 +108,13 @@ class CacheStrategyTest { val files = createTempFilesSortByOldestToNewest() val okSession = createSessionMockData(Session.State.Ok, true) - val okEnvelope = SentryEnvelope.from(sut.serializer, okSession, null) - sut.serializer.serialize(okEnvelope, files[0].outputStream()) + val okEnvelope = SentryEnvelope.from(sut.serializer.value, okSession, null) + sut.serializer.value.serialize(okEnvelope, files[0].outputStream()) val updatedOkSession = okSession.clone() updatedOkSession.update(null, null, true) - val updatedOkEnvelope = SentryEnvelope.from(sut.serializer, updatedOkSession, null) - sut.serializer.serialize(updatedOkEnvelope, files[1].outputStream()) + val updatedOkEnvelope = SentryEnvelope.from(sut.serializer.value, updatedOkSession, null) + sut.serializer.value.serialize(updatedOkEnvelope, files[1].outputStream()) saveSessionToFile(files[2], sut, Session.State.Exited, null) @@ -178,17 +178,17 @@ class CacheStrategyTest { ) private fun getSessionFromFile(file: File, sut: CacheStrategy): Session { - val envelope = sut.serializer.deserializeEnvelope(file.inputStream()) + val envelope = sut.serializer.value.deserializeEnvelope(file.inputStream()) val item = envelope!!.items.first() val reader = InputStreamReader(ByteArrayInputStream(item.data), Charsets.UTF_8) - return sut.serializer.deserialize(reader, Session::class.java)!! + return sut.serializer.value.deserialize(reader, Session::class.java)!! } private fun saveSessionToFile(file: File, sut: CacheStrategy, state: Session.State = Session.State.Ok, init: Boolean? = true) { val okSession = createSessionMockData(state, init) - val okEnvelope = SentryEnvelope.from(sut.serializer, okSession, null) - sut.serializer.serialize(okEnvelope, file.outputStream()) + val okEnvelope = SentryEnvelope.from(sut.serializer.value, okSession, null) + sut.serializer.value.serialize(okEnvelope, file.outputStream()) } private fun getOptionsWithRealSerializer(): SentryOptions {