Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't wait on main thread when SDK restarts #3200

Merged
merged 12 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Add new threshold parameters to monitor config ([#3181](https://github.com/getsentry/sentry-java/pull/3181))

### Fixes

- Don't wait on main thread when SDK restarts ([#3200](https://github.com/getsentry/sentry-java/pull/3200))

## 7.3.0

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class InternalSentrySdkTest {
options.dsn = "https://key@host/proj"
options.setTransportFactory { _, _ ->
object : ITransport {
override fun close(isRestarting: Boolean) {
// no-op
}

override fun close() {
// no-op
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ class SessionTrackingIntegrationTest {
TODO("Not yet implemented")
}

override fun close(isRestarting: Boolean) {
TODO("Not yet implemented")
}

override fun close() {
TODO("Not yet implemented")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.sentry.uitest.android

import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso
Expand Down Expand Up @@ -199,7 +198,6 @@ class EnvelopeTests : BaseUiTest() {

relay.assert {
findEnvelope {
Log.e("ITEMS", it.items.joinToString { item -> item.header.type.itemType })
assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction == "timedOutProfile"
}.assert {
val transactionItem: SentryTransaction = it.assertTransaction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import io.sentry.android.core.AndroidLogger
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.assertEnvelopeTransaction
import io.sentry.protocol.SentryTransaction
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.SocketPolicy
import org.junit.runner.RunWith
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
class SdkInitTests : BaseUiTest() {
Expand Down Expand Up @@ -74,4 +77,109 @@ class SdkInitTests : BaseUiTest() {
assertNoOtherEnvelopes()
}
}

@Test
fun doubleInitDoesNotWait() {
relayIdlingResource.increment()
relayIdlingResource.increment()
// Let's make the first request timeout
relay.addResponse { MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE) }

initSentry(true) { options: SentryAndroidOptions ->
options.tracesSampleRate = 1.0
}

Sentry.startTransaction("beforeRestart", "emptyTransaction").finish()

// We want the SDK to start sending the event. If we don't wait, it's possible we don't send anything before the SDK is restarted
Thread.sleep(500)
markushi marked this conversation as resolved.
Show resolved Hide resolved

val beforeRestart = System.currentTimeMillis()
// We restart the SDK. This shouldn't block the main thread, but new options (e.g. profiling) should work
initSentry(false) { options: SentryAndroidOptions ->
// Registering again the idling resource blocks for some time. But we already registered it before.
// So we just need to update the mock relay flag, as we are overwriting it in this "initSentry(false)".
relay.waitForRequests = true
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
}
val afterRestart = System.currentTimeMillis()
val restartMs = afterRestart - beforeRestart

Sentry.startTransaction("afterRestart", "emptyTransaction").finish()
// We assert for less than 1 second just to account for slow devices in saucelabs or headless emulator
assertTrue(restartMs < 1000, "Expected less than 1000 ms for SDK restart. Got $restartMs ms")

relay.assert {
findEnvelope {
assertEnvelopeTransaction(it.items.toList()).transaction == "beforeRestart"
}.assert {
it.assertTransaction()
// No profiling item, as in the first init it was not enabled
it.assertNoOtherItems()
}
findEnvelope {
assertEnvelopeTransaction(it.items.toList()).transaction == "afterRestart"
}.assert {
it.assertTransaction()
// There is a profiling item, as in the second init it was enabled
it.assertProfile()
it.assertNoOtherItems()
}
assertNoOtherEnvelopes()
}
}

@Test
fun initCloseInitWaits() {
relayIdlingResource.increment()
relayIdlingResource.increment()
// Let's make the first request timeout
relay.addResponse { MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE) }

initSentry(true) { options: SentryAndroidOptions ->
options.tracesSampleRate = 1.0
options.flushTimeoutMillis = 3000
}

Sentry.startTransaction("beforeRestart", "emptyTransaction").finish()

// We want the SDK to start sending the event. If we don't wait, it's possible we don't send anything before the SDK is restarted
Thread.sleep(500)

val beforeRestart = System.currentTimeMillis()
Sentry.close()
// We stop the SDK. This should block the main thread. Then we start it again with new options
initSentry(false) { options: SentryAndroidOptions ->
// Registering again the idling resource blocks for some time. But we already registered it before.
// So we just need to update the mock relay flag, as we are overwriting it in this "initSentry(false)".
relay.waitForRequests = true
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
}
val afterRestart = System.currentTimeMillis()
val restartMs = afterRestart - beforeRestart

Sentry.startTransaction("afterRestart", "emptyTransaction").finish()
assertTrue(restartMs > 3000, "Expected more than 3000 ms for SDK close and restart. Got $restartMs ms")

relay.assert {
findEnvelope {
assertEnvelopeTransaction(it.items.toList()).transaction == "beforeRestart"
}.assert {
it.assertTransaction()
// No profiling item, as in the first init it was not enabled
it.assertNoOtherItems()
}
findEnvelope {
assertEnvelopeTransaction(it.items.toList()).transaction == "afterRestart"
}.assert {
it.assertTransaction()
// There is a profiling item, as in the second init it was enabled
it.assertProfile()
it.assertNoOtherItems()
}
assertNoOtherEnvelopes()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
public final class io/sentry/transport/apache/ApacheHttpClientTransport : io/sentry/transport/ITransport {
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/RequestDetails;Lorg/apache/hc/client5/http/impl/async/CloseableHttpAsyncClient;Lio/sentry/transport/RateLimiter;)V
public fun close ()V
public fun close (Z)V
public fun flush (J)V
public fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public fun send (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,14 @@ public void flush(long timeoutMillis) {

@Override
public void close() throws IOException {
close(false);
}

@Override
public void close(final boolean isRestarting) throws IOException {
options.getLogger().log(DEBUG, "Shutting down");
try {
httpclient.awaitShutdown(TimeValue.ofSeconds(1));
httpclient.awaitShutdown(TimeValue.ofSeconds(isRestarting ? 0 : 1));
} catch (InterruptedException e) {
options.getLogger().log(DEBUG, "Thread interrupted while closing the connection.");
Thread.currentThread().interrupt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import java.util.concurrent.Executors
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals

class ApacheHttpClientTransportTest {

Expand Down Expand Up @@ -116,7 +117,21 @@ class ApacheHttpClientTransportTest {
fun `close waits for shutdown`() {
val sut = fixture.getSut()
sut.close()
verify(fixture.client).awaitShutdown(any())
verify(fixture.client).awaitShutdown(check { assertNotEquals(0L, it.duration) })
}

@Test
fun `close with isRestarting false waits for shutdown`() {
val sut = fixture.getSut()
sut.close(false)
verify(fixture.client).awaitShutdown(check { assertNotEquals(0L, it.duration) })
}

@Test
fun `close with isRestarting true does not wait for shutdown`() {
val sut = fixture.getSut()
sut.close(true)
verify(fixture.client).awaitShutdown(check { assertEquals(0L, it.duration) })
}

@Test
Expand Down
10 changes: 10 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ public final class io/sentry/Hub : io/sentry/IHub {
public fun clone ()Lio/sentry/IHub;
public synthetic fun clone ()Ljava/lang/Object;
public fun close ()V
public fun close (Z)V
public fun configureScope (Lio/sentry/ScopeCallback;)V
public fun continueTrace (Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;
public fun endSession ()V
Expand Down Expand Up @@ -477,6 +478,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub {
public fun clone ()Lio/sentry/IHub;
public synthetic fun clone ()Ljava/lang/Object;
public fun close ()V
public fun close (Z)V
public fun configureScope (Lio/sentry/ScopeCallback;)V
public fun continueTrace (Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;
public fun endSession ()V
Expand Down Expand Up @@ -567,6 +569,7 @@ public abstract interface class io/sentry/IHub {
public abstract fun clearBreadcrumbs ()V
public abstract fun clone ()Lio/sentry/IHub;
public abstract fun close ()V
public abstract fun close (Z)V
public abstract fun configureScope (Lio/sentry/ScopeCallback;)V
public abstract fun continueTrace (Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;
public abstract fun endSession ()V
Expand Down Expand Up @@ -732,6 +735,7 @@ public abstract interface class io/sentry/ISentryClient {
public abstract fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId;
public abstract fun captureUserFeedback (Lio/sentry/UserFeedback;)V
public abstract fun close ()V
public abstract fun close (Z)V
public abstract fun flush (J)V
public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public abstract fun isEnabled ()Z
Expand Down Expand Up @@ -1131,6 +1135,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub {
public fun clone ()Lio/sentry/IHub;
public synthetic fun clone ()Ljava/lang/Object;
public fun close ()V
public fun close (Z)V
public fun configureScope (Lio/sentry/ScopeCallback;)V
public fun continueTrace (Ljava/lang/String;Ljava/util/List;)Lio/sentry/TransactionContext;
public fun endSession ()V
Expand Down Expand Up @@ -1849,6 +1854,7 @@ public final class io/sentry/SentryClient : io/sentry/ISentryClient {
public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId;
public fun captureUserFeedback (Lio/sentry/UserFeedback;)V
public fun close ()V
public fun close (Z)V
public fun flush (J)V
public fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public fun isEnabled ()Z
Expand Down Expand Up @@ -4536,6 +4542,7 @@ public final class io/sentry/transport/AsyncHttpTransport : io/sentry/transport/
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/transport/RateLimiter;Lio/sentry/transport/ITransportGate;Lio/sentry/RequestDetails;)V
public fun <init> (Lio/sentry/transport/QueuedThreadPoolExecutor;Lio/sentry/SentryOptions;Lio/sentry/transport/RateLimiter;Lio/sentry/transport/ITransportGate;Lio/sentry/transport/HttpConnection;)V
public fun close ()V
public fun close (Z)V
public fun flush (J)V
public fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public fun isHealthy ()Z
Expand All @@ -4552,6 +4559,7 @@ public abstract interface class io/sentry/transport/ICurrentDateProvider {
}

public abstract interface class io/sentry/transport/ITransport : java/io/Closeable {
public abstract fun close (Z)V
public abstract fun flush (J)V
public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public fun isHealthy ()Z
Expand All @@ -4573,6 +4581,7 @@ public final class io/sentry/transport/NoOpEnvelopeCache : io/sentry/cache/IEnve

public final class io/sentry/transport/NoOpTransport : io/sentry/transport/ITransport {
public fun close ()V
public fun close (Z)V
public fun flush (J)V
public static fun getInstance ()Lio/sentry/transport/NoOpTransport;
public fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
Expand Down Expand Up @@ -4606,6 +4615,7 @@ public final class io/sentry/transport/ReusableCountLatch {
public final class io/sentry/transport/StdoutTransport : io/sentry/transport/ITransport {
public fun <init> (Lio/sentry/ISerializer;)V
public fun close ()V
public fun close (Z)V
public fun flush (J)V
public fun getRateLimiter ()Lio/sentry/transport/RateLimiter;
public fun send (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
Expand Down
15 changes: 13 additions & 2 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ public void endSession() {

@Override
public void close() {
close(false);
}

@Override
@SuppressWarnings("FutureReturnValueIgnored")
public void close(final boolean isRestarting) {
if (!isEnabled()) {
options
.getLogger()
Expand All @@ -352,12 +358,17 @@ public void close() {
configureScope(scope -> scope.clear());
options.getTransactionProfiler().close();
options.getTransactionPerformanceCollector().close();
options.getExecutorService().close(options.getShutdownTimeoutMillis());
final @NotNull ISentryExecutorService executorService = options.getExecutorService();
if (isRestarting) {
executorService.submit(() -> executorService.close(options.getShutdownTimeoutMillis()));
} else {
executorService.close(options.getShutdownTimeoutMillis());
}

// Close the top-most client
final StackItem item = stack.peek();
// TODO: should we end session before closing client?
item.getClient().close();
item.getClient().close(isRestarting);
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Error while closing the Hub.", e);
}
Expand Down
5 changes: 5 additions & 0 deletions sentry/src/main/java/io/sentry/HubAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public void endSession() {
Sentry.endSession();
}

@Override
public void close(final boolean isRestarting) {
Sentry.close();
}

@Override
public void close() {
Sentry.close();
Expand Down
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/IHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ SentryId captureException(
/** Flushes out the queue for up to timeout seconds and disable the Hub. */
void close();

/**
* Flushes out the queue for up to timeout seconds and disable the Hub.
*
* @param isRestarting if true, avoids locking the main thread when finishing the queue.
*/
void close(boolean isRestarting);

/**
* Adds a breadcrumb to the current Scope
*
Expand Down
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/ISentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public interface ISentryClient {
/** Flushes out the queue for up to timeout seconds and disable the client. */
void close();

/**
* Flushes out the queue for up to timeout seconds and disable the client.
*
* @param isRestarting if true, avoids locking the main thread when finishing the queue.
*/
void close(boolean isRestarting);

/**
* Flushes events queued up, but keeps the client enabled. Not implemented yet.
*
Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public void endSession() {}
@Override
public void close() {}

@Override
public void close(final boolean isRestarting) {}

@Override
public void addBreadcrumb(@NotNull Breadcrumb breadcrumb, @Nullable Hint hint) {}

Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpSentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public boolean isEnabled() {
return SentryId.EMPTY_ID;
}

@Override
public void close(final boolean isRestarting) {}

@Override
public void close() {}

Expand Down
Loading
Loading