Skip to content

Commit 651fa3d

Browse files
Fix potential memory leaks. (#1909)
1 parent 33bdce3 commit 651fa3d

File tree

7 files changed

+76
-5
lines changed

7 files changed

+76
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* Fix: Do not include stacktrace frames into Timber message (#1898)
6+
* Fix: Potential memory leaks (#1909)
67

78
Breaking changes:
89
`Timber.tag` is no longer supported by our [Timber integration](https://docs.sentry.io/platforms/android/configuration/integrations/timber/) and will not appear on Sentry for error events.

sentry-spring/api/sentry-spring.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ public class io/sentry/spring/SentryHubRegistrar : org/springframework/context/a
2727
public fun registerBeanDefinitions (Lorg/springframework/core/type/AnnotationMetadata;Lorg/springframework/beans/factory/support/BeanDefinitionRegistry;)V
2828
}
2929

30-
public class io/sentry/spring/SentryInitBeanPostProcessor : org/springframework/beans/factory/config/BeanPostProcessor, org/springframework/context/ApplicationContextAware {
30+
public class io/sentry/spring/SentryInitBeanPostProcessor : org/springframework/beans/factory/DisposableBean, org/springframework/beans/factory/config/BeanPostProcessor, org/springframework/context/ApplicationContextAware {
3131
public fun <init> ()V
32+
public fun destroy ()V
3233
public fun postProcessAfterInitialization (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
3334
public fun setApplicationContext (Lorg/springframework/context/ApplicationContext;)V
3435
}

sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,42 @@
22

33
import com.jakewharton.nopen.annotation.Open;
44
import io.sentry.EventProcessor;
5+
import io.sentry.HubAdapter;
6+
import io.sentry.IHub;
57
import io.sentry.ITransportFactory;
68
import io.sentry.Integration;
79
import io.sentry.Sentry;
810
import io.sentry.SentryOptions;
911
import io.sentry.SentryOptions.TracesSamplerCallback;
12+
import io.sentry.util.Objects;
1013
import org.jetbrains.annotations.NotNull;
1114
import org.jetbrains.annotations.Nullable;
1215
import org.springframework.beans.BeansException;
16+
import org.springframework.beans.factory.DisposableBean;
1317
import org.springframework.beans.factory.config.BeanPostProcessor;
1418
import org.springframework.context.ApplicationContext;
1519
import org.springframework.context.ApplicationContextAware;
1620

17-
/** Initializes Sentry after all beans are registered. */
21+
/**
22+
* Initializes Sentry after all beans are registered. Closes Sentry on Spring application context
23+
* destroy.
24+
*/
1825
@Open
19-
public class SentryInitBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
26+
public class SentryInitBeanPostProcessor
27+
implements BeanPostProcessor, ApplicationContextAware, DisposableBean {
2028
private @Nullable ApplicationContext applicationContext;
2129

30+
private final @NotNull IHub hub;
31+
32+
public SentryInitBeanPostProcessor() {
33+
this(HubAdapter.getInstance());
34+
}
35+
36+
SentryInitBeanPostProcessor(final @NotNull IHub hub) {
37+
Objects.requireNonNull(hub, "hub is required");
38+
this.hub = hub;
39+
}
40+
2241
@Override
2342
@SuppressWarnings({"unchecked", "deprecation"})
2443
public @NotNull Object postProcessAfterInitialization(
@@ -68,4 +87,9 @@ public void setApplicationContext(final @NotNull ApplicationContext applicationC
6887
throws BeansException {
6988
this.applicationContext = applicationContext;
7089
}
90+
91+
@Override
92+
public void destroy() {
93+
hub.close();
94+
}
7195
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.sentry.spring
2+
3+
import com.nhaarman.mockitokotlin2.mock
4+
import com.nhaarman.mockitokotlin2.verify
5+
import io.sentry.IHub
6+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
7+
import org.springframework.context.annotation.Bean
8+
import org.springframework.context.annotation.Configuration
9+
import kotlin.test.Test
10+
11+
class SentryInitBeanPostProcessorTest {
12+
13+
@Test
14+
fun closesSentryOnApplicationContextDestroy() {
15+
val ctx = AnnotationConfigApplicationContext(TestConfig::class.java)
16+
val hub = ctx.getBean(IHub::class.java)
17+
ctx.close()
18+
verify(hub).close()
19+
}
20+
21+
@Configuration
22+
open class TestConfig {
23+
24+
@Bean(destroyMethod = "")
25+
open fun hub() = mock<IHub>()
26+
27+
@Bean
28+
open fun sentryInitBeanPostProcessor() = SentryInitBeanPostProcessor(hub())
29+
}
30+
}

sentry/src/main/java/io/sentry/HostnameCache.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@
2121
* by keeping track of the hostname for a period defined during the construction. For performance
2222
* purposes, the operation of retrieving the hostname will automatically fail after a period of time
2323
* defined by {@link #GET_HOSTNAME_TIMEOUT} without result.
24+
*
25+
* <p>HostnameCache is a singleton and its instance should be obtained through {@link
26+
* HostnameCache#getInstance()}.
2427
*/
2528
final class HostnameCache {
2629
private static final long HOSTNAME_CACHE_DURATION = TimeUnit.HOURS.toMillis(5);
2730

2831
/** Time before the get hostname operation times out (in ms). */
2932
private static final long GET_HOSTNAME_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
33+
34+
@Nullable private static HostnameCache INSTANCE;
35+
3036
/** Time for which the cache is kept. */
3137
private final long cacheDuration;
3238
/** Current value for hostname (might change over time). */
@@ -41,7 +47,14 @@ final class HostnameCache {
4147
private final @NotNull ExecutorService executorService =
4248
Executors.newSingleThreadExecutor(new HostnameCacheThreadFactory());
4349

44-
public HostnameCache() {
50+
static @NotNull HostnameCache getInstance() {
51+
if (INSTANCE == null) {
52+
INSTANCE = new HostnameCache();
53+
}
54+
return INSTANCE;
55+
}
56+
57+
private HostnameCache() {
4558
this(HOSTNAME_CACHE_DURATION);
4659
}
4760

sentry/src/main/java/io/sentry/MainEventProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public final class MainEventProcessor implements EventProcessor, Closeable {
3434
private final @Nullable HostnameCache hostnameCache;
3535

3636
MainEventProcessor(final @NotNull SentryOptions options) {
37-
this(options, options.isAttachServerName() ? new HostnameCache() : null);
37+
this(options, options.isAttachServerName() ? HostnameCache.getInstance() : null);
3838
}
3939

4040
MainEventProcessor(

sentry/src/main/java/io/sentry/Sentry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ private static boolean initConfigurations(final @NotNull SentryOptions options)
237237
public static synchronized void close() {
238238
final IHub hub = getCurrentHub();
239239
mainHub = NoOpHub.getInstance();
240+
// remove thread local to avoid memory leak
241+
currentHub.remove();
240242
hub.close();
241243
}
242244

0 commit comments

Comments
 (0)