diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 24935be274..32ce777a3d 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -259,6 +259,13 @@ public final class io/sentry/DeduplicateMultithreadedEventProcessor : io/sentry/ public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; } +public final class io/sentry/DefaultScopesStorage : io/sentry/IScopesStorage { + public fun ()V + public fun close ()V + public fun get ()Lio/sentry/IScopes; + public fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken; +} + public final class io/sentry/DefaultTransactionPerformanceCollector : io/sentry/TransactionPerformanceCollector { public fun (Lio/sentry/SentryOptions;)V public fun close ()V @@ -792,6 +799,12 @@ public abstract interface class io/sentry/IScopes { public abstract fun withScope (Lio/sentry/ScopeCallback;)V } +public abstract interface class io/sentry/IScopesStorage { + public abstract fun close ()V + public abstract fun get ()Lio/sentry/IScopes; + public abstract fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken; +} + public abstract interface class io/sentry/ISentryClient { public abstract fun captureCheckIn (Lio/sentry/CheckIn;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureEnvelope (Lio/sentry/SentryEnvelope;)Lio/sentry/protocol/SentryId; @@ -831,6 +844,10 @@ public abstract interface class io/sentry/ISentryExecutorService { public abstract fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; } +public abstract interface class io/sentry/ISentryLifecycleToken : java/lang/AutoCloseable { + public abstract fun close ()V +} + public abstract interface class io/sentry/ISerializer { public abstract fun deserialize (Ljava/io/Reader;Ljava/lang/Class;)Ljava/lang/Object; public abstract fun deserializeCollection (Ljava/io/Reader;Ljava/lang/Class;Lio/sentry/JsonDeserializer;)Ljava/lang/Object; @@ -1390,6 +1407,13 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun withScope (Lio/sentry/ScopeCallback;)V } +public final class io/sentry/NoOpScopesStorage : io/sentry/IScopesStorage { + public fun close ()V + public fun get ()Lio/sentry/IScopes; + public static fun getInstance ()Lio/sentry/NoOpScopesStorage; + public fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken; +} + public final class io/sentry/NoOpSpan : io/sentry/ISpan { public fun finish ()V public fun finish (Lio/sentry/SpanStatus;)V diff --git a/sentry/src/main/java/io/sentry/DefaultScopesStorage.java b/sentry/src/main/java/io/sentry/DefaultScopesStorage.java new file mode 100644 index 0000000000..12902a1dff --- /dev/null +++ b/sentry/src/main/java/io/sentry/DefaultScopesStorage.java @@ -0,0 +1,42 @@ +package io.sentry; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class DefaultScopesStorage implements IScopesStorage { + + private static final @NotNull ThreadLocal currentScopes = new ThreadLocal<>(); + + @Override + public ISentryLifecycleToken set(@Nullable IScopes scopes) { + final @Nullable IScopes oldScopes = get(); + currentScopes.set(scopes); + return new DefaultScopesLifecycleToken(oldScopes); + } + + @Override + public @Nullable IScopes get() { + return currentScopes.get(); + } + + @Override + public void close() { + // TODO prevent further storing? would this cause problems if singleton, closed and + // re-initialized? + currentScopes.remove(); + } + + static final class DefaultScopesLifecycleToken implements ISentryLifecycleToken { + + private final @Nullable IScopes oldValue; + + DefaultScopesLifecycleToken(final @Nullable IScopes scopes) { + this.oldValue = scopes; + } + + @Override + public void close() { + currentScopes.set(oldValue); + } + } +} diff --git a/sentry/src/main/java/io/sentry/IScopesStorage.java b/sentry/src/main/java/io/sentry/IScopesStorage.java new file mode 100644 index 0000000000..92f6b587c4 --- /dev/null +++ b/sentry/src/main/java/io/sentry/IScopesStorage.java @@ -0,0 +1,13 @@ +package io.sentry; + +import org.jetbrains.annotations.Nullable; + +public interface IScopesStorage { + + ISentryLifecycleToken set(final @Nullable IScopes scopes); + + @Nullable + IScopes get(); + + void close(); +} diff --git a/sentry/src/main/java/io/sentry/ISentryLifecycleToken.java b/sentry/src/main/java/io/sentry/ISentryLifecycleToken.java new file mode 100644 index 0000000000..2d0ad180f7 --- /dev/null +++ b/sentry/src/main/java/io/sentry/ISentryLifecycleToken.java @@ -0,0 +1,8 @@ +package io.sentry; + +public interface ISentryLifecycleToken extends AutoCloseable { + + // overridden to not have a checked exception on the method. + @Override + void close(); +} diff --git a/sentry/src/main/java/io/sentry/NoOpScopesStorage.java b/sentry/src/main/java/io/sentry/NoOpScopesStorage.java new file mode 100644 index 0000000000..fa507987ae --- /dev/null +++ b/sentry/src/main/java/io/sentry/NoOpScopesStorage.java @@ -0,0 +1,40 @@ +package io.sentry; + +import org.jetbrains.annotations.Nullable; + +public final class NoOpScopesStorage implements IScopesStorage { + private static final NoOpScopesStorage instance = new NoOpScopesStorage(); + + private NoOpScopesStorage() {} + + public static NoOpScopesStorage getInstance() { + return instance; + } + + @Override + public ISentryLifecycleToken set(@Nullable IScopes scopes) { + return NoOpScopesLifecycleToken.getInstance(); + } + + @Override + public @Nullable IScopes get() { + return NoOpScopes.getInstance(); + } + + @Override + public void close() {} + + static final class NoOpScopesLifecycleToken implements ISentryLifecycleToken { + + private static final NoOpScopesLifecycleToken instance = new NoOpScopesLifecycleToken(); + + private NoOpScopesLifecycleToken() {} + + public static NoOpScopesLifecycleToken getInstance() { + return instance; + } + + @Override + public void close() {} + } +} diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index f4996bb8ad..e01ca2f281 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -43,8 +43,7 @@ public final class Sentry { private Sentry() {} - /** Holds Hubs per thread or only mainScopes if globalHubMode is enabled. */ - private static final @NotNull ThreadLocal currentScopes = new ThreadLocal<>(); + private static volatile @NotNull IScopesStorage scopesStorage = new DefaultScopesStorage(); /** The Main Hub or NoOp if Sentry is disabled. */ private static volatile @NotNull IScopes mainScopes = NoOpScopes.getInstance(); @@ -83,13 +82,17 @@ private Sentry() {} if (globalHubMode) { return mainScopes; } - IScopes hub = currentScopes.get(); - if (hub == null || hub.isNoOp()) { + IScopes scopes = getScopesStorage().get(); + if (scopes == null || scopes.isNoOp()) { // TODO fork instead - hub = mainScopes.clone(); - currentScopes.set(hub); + scopes = mainScopes.clone(); + getScopesStorage().set(scopes); } - return hub; + return scopes; + } + + private static @NotNull IScopesStorage getScopesStorage() { + return scopesStorage; } /** @@ -110,14 +113,15 @@ private Sentry() {} @ApiStatus.Internal // exposed for the coroutines integration in SentryContext @Deprecated - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "InlineMeSuggester"}) public static void setCurrentHub(final @NotNull IHub hub) { - currentScopes.set(hub); + setCurrentScopes(hub); } @ApiStatus.Internal // exposed for the coroutines integration in SentryContext - public static void setCurrentScopes(final @NotNull IScopes scopes) { - currentScopes.set(scopes); + public static @NotNull ISentryLifecycleToken setCurrentScopes(final @NotNull IScopes scopes) { + return getScopesStorage().set(scopes); + } } /** @@ -253,14 +257,18 @@ private static synchronized void init( options.getLogger().log(SentryLevel.INFO, "GlobalHubMode: '%s'", String.valueOf(globalHubMode)); Sentry.globalHubMode = globalHubMode; - final IScopes hub = getCurrentScopes(); + final IScopes scopes = getCurrentScopes(); final IScope rootScope = new Scope(options); - final IScope rootIsolationScope = new Scope(options); - mainScopes = new Scopes(rootScope, rootIsolationScope, options, "Sentry.init"); + // TODO should use separate isolation scope: + // final IScope rootIsolationScope = new Scope(options); + // TODO should be: + // getGlobalScope().bindClient(new SentryClient(options)); + rootScope.bindClient(new SentryClient(options)); + mainScopes = new Scopes(rootScope, rootScope, options, "Sentry.init"); - currentScopes.set(mainScopes); + getScopesStorage().set(mainScopes); - hub.close(true); + scopes.close(true); // If the executorService passed in the init is the same that was previously closed, we have to // set a new one @@ -508,7 +516,7 @@ public static synchronized void close() { final IScopes scopes = getCurrentScopes(); mainScopes = NoOpScopes.getInstance(); // remove thread local to avoid memory leak - currentScopes.remove(); + getScopesStorage().close(); scopes.close(false); }