Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Call Sentry#close on JVM shutdown. #497

Merged
merged 6 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.sentry.core.ILogger;
import io.sentry.core.SendCachedEventFireAndForgetIntegration;
import io.sentry.core.SendFireAndForgetEnvelopeSender;
import io.sentry.core.SendFireAndForgetEventSender;
import io.sentry.core.SentryLevel;
import io.sentry.core.SentryOptions;
import io.sentry.core.util.Objects;
Expand Down Expand Up @@ -115,6 +116,14 @@ private static void installDefaultIntegrations(
final @NotNull IBuildInfoProvider buildInfoProvider,
final @NotNull ILoadClass loadClass) {

options.addIntegration(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, for now it makes sense to just add the "event cache" related stuff into Android and focus on the core not having offline caching altogether.

Down the road when the need comes we can revisit and think of a better way to "opt-in" to offline mode in a simple way, that would work for Android or Java apps (say desktop could leverage that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Just so that it doesn't get lost - I think we should expose ideally single method that turns on caching which takes cachePathDir as parameter and under the hood also turns on all cache-related integrations.

new SendCachedEventFireAndForgetIntegration(
new SendFireAndForgetEventSender(() -> options.getCacheDirPath())));

options.addIntegration(
new SendCachedEventFireAndForgetIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getSessionsPath())));

// Integrations are registered in the same order. NDK before adding Watch outbox,
// because sentry-native move files around and we don't want to watch that.
final Class<?> sentryNdkClass = loadNdkIfAvailable(options, buildInfoProvider, loadClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.core.ILogger
import io.sentry.core.MainEventProcessor
import io.sentry.core.SendCachedEventFireAndForgetIntegration
import io.sentry.core.SentryLevel
import io.sentry.core.SentryOptions
import java.io.File
Expand Down Expand Up @@ -269,6 +270,16 @@ class AndroidOptionsInitializerTest {
assertNotNull(actual)
}

@Test
fun `SendCachedEventFireAndForgetIntegration added to integration list`() {
val sentryOptions = SentryAndroidOptions()
val mockContext = createMockContext()

AndroidOptionsInitializer.init(sentryOptions, mockContext)
val actual = sentryOptions.integrations.firstOrNull { it is SendCachedEventFireAndForgetIntegration }
assertNotNull(actual)
}

@Test
fun `When given Context returns a non null ApplicationContext, uses it`() {
val sentryOptions = SentryAndroidOptions()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package io.sentry.core;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

final class SendFireAndForgetEventSender
@ApiStatus.Internal
public final class SendFireAndForgetEventSender
implements SendCachedEventFireAndForgetIntegration.SendFireAndForgetFactory {

private final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath
sendFireAndForgetDirPath;

SendFireAndForgetEventSender(
public SendFireAndForgetEventSender(
final @NotNull SendCachedEventFireAndForgetIntegration.SendFireAndForgetDirPath
sendFireAndForgetDirPath) {
this.sendFireAndForgetDirPath = sendFireAndForgetDirPath;
Expand Down
10 changes: 2 additions & 8 deletions sentry-core/src/main/java/io/sentry/core/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -1033,14 +1033,8 @@ public SentryOptions() {
// if there's an error on the setup, we are able to capture it
integrations.add(new UncaughtExceptionHandlerIntegration());

eventProcessors.add(new MainEventProcessor(this));

integrations.add(
new SendCachedEventFireAndForgetIntegration(
new SendFireAndForgetEventSender(() -> getCacheDirPath())));
integrations.add(new ShutdownHookIntegration(Runtime.getRuntime()));

integrations.add(
new SendCachedEventFireAndForgetIntegration(
new SendFireAndForgetEnvelopeSender(() -> getSessionsPath())));
eventProcessors.add(new MainEventProcessor(this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.sentry.core;

import io.sentry.core.util.Objects;
import org.jetbrains.annotations.NotNull;

/** Registers hook that closes {@link Hub} when main thread shuts down. */
public final class ShutdownHookIntegration implements Integration {

private final @NotNull Runtime runtime;

public ShutdownHookIntegration(final @NotNull Runtime runtime) {
this.runtime = Objects.requireNonNull(runtime, "Runtime is required");
}
maciejwalkowiak marked this conversation as resolved.
Show resolved Hide resolved

@Override
public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to not apply the final modifier?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No specific reason, I've seen some methods with final and some without. I'll add them.

My personal take on final on method parameters is that they clutter code and instead all parameters should be treated as final implicitly. That's just an opinion, I will adjust to existing coding standards.

Copy link
Contributor

@marandaneto marandaneto Jul 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, just asking cus it was in my first GH "suggestion".

Agreed that they should be treated as final implicitly, but IMO when you have multithread systems, having an extra final makes the compiler to help you out if you are not cloning/deep copying an object but instead messing up with the references, we had a bug like this a few months ago, so we've decided to add in all the missing ones/and new ones

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there is a way to configure static code analysis to make sure it doesn't sneak in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the data classes are mutable by design, I've tried once but then it warned about almost everything.
so basically what I am doing is, new things I always try to make everything final if it can and if I am touching some classes, I already check the missing final and @NotNull, @Nullable to make better interoperability with Kotlin tests

Objects.requireNonNull(hub, "Hub is required");

runtime.addShutdownHook(new Thread(() -> hub.close()));
}
}
5 changes: 5 additions & 0 deletions sentry-core/src/test/java/io/sentry/core/SentryOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class SentryOptionsTest {
assertTrue(SentryOptions().integrations.any { it is UncaughtExceptionHandlerIntegration })
}

@Test
fun `when options is initialized, integrations contain ShutdownHookIntegration`() {
assertTrue(SentryOptions().integrations.any { it is ShutdownHookIntegration })
}

@Test
fun `when options is initialized, default maxBreadcrumb is 100`() =
assertEquals(100, SentryOptions().maxBreadcrumbs)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.sentry.core

import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlin.test.Test

class ShutdownHookIntegrationTest {

@Test
fun `registration attaches shutdown hook to runtime`() {
val runtime = mock<Runtime>()
val integration = ShutdownHookIntegration(runtime)

integration.register(NoOpHub.getInstance(), SentryOptions())

verify(runtime).addShutdownHook(any())
}
}