Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add deadlineTimeout option ([#4555](https://github.com/getsentry/sentry-java/pull/4555))

### Fixes

- Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ private void startTracing(final @NotNull Activity activity) {
}

final TransactionOptions transactionOptions = new TransactionOptions();

// Set deadline timeout based on configured option
final long deadlineTimeoutMillis = options.getDeadlineTimeout();
// No deadline when zero or negative value is set
transactionOptions.setDeadlineTimeout(
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis);

if (options.isEnableActivityLifecycleTracingAutoFinish()) {
transactionOptions.setIdleTimeout(options.getIdleTimeout());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ final class ManifestMetadataReader {
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
"io.sentry.traces.enable-auto-id-generation";

static final String DEADLINE_TIMEOUT = "io.sentry.traces.deadline-timeout";

static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required";

static final String FEEDBACK_SHOW_NAME = "io.sentry.feedback.show-name";
Expand Down Expand Up @@ -446,6 +448,9 @@ static void applyMetadata(
ENABLE_AUTO_TRACE_ID_GENERATION,
options.isEnableAutoTraceIdGeneration()));

options.setDeadlineTimeout(
readLong(metadata, logger, DEADLINE_TIMEOUT, options.getDeadlineTimeout()));

if (options.getSessionReplay().getSessionSampleRate() == null) {
final double sessionSampleRate =
readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,13 @@ private void startTracing(final @NotNull UiElement target, final @NotNull Gestur

final TransactionOptions transactionOptions = new TransactionOptions();
transactionOptions.setWaitForChildren(true);

// Set deadline timeout based on configured option
final long deadlineTimeoutMillis = options.getDeadlineTimeout();
// No deadline when zero or negative value is set
transactionOptions.setDeadlineTimeout(
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis);

transactionOptions.setIdleTimeout(options.getIdleTimeout());
transactionOptions.setTrimEnd(true);
transactionOptions.setOrigin(TRACE_ORIGIN + "." + target.getOrigin());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,80 @@ class ActivityLifecycleIntegrationTest {
)
}

@Test
fun `Activity transaction uses custom deadline timeout when autoTransactionDeadlineTimeoutMillis is set to positive value`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.deadlineTimeout = 60000L // 60 seconds

sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(mock(), fixture.bundle)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { transactionOptions ->
assertEquals(60000L, transactionOptions.deadlineTimeout)
},
)
}

@Test
fun `Activity transaction uses no deadline timeout when autoTransactionDeadlineTimeoutMillis is set to zero`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.deadlineTimeout = 0L // No deadline

sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(mock(), fixture.bundle)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { transactionOptions ->
assertNull(transactionOptions.deadlineTimeout)
},
)
}

@Test
fun `Activity transaction uses no deadline timeout when autoTransactionDeadlineTimeoutMillis is set to negative value`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.deadlineTimeout = -1L // No deadline

sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(mock(), fixture.bundle)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { transactionOptions ->
assertNull(transactionOptions.deadlineTimeout)
},
)
}

@Test
fun `Activity transaction uses default deadline timeout when autoTransactionDeadlineTimeoutMillis is default`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0

sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(mock(), fixture.bundle)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { transactionOptions ->
assertEquals(
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION,
transactionOptions.deadlineTimeout,
)
},
)
}

@Test
fun `Activity gets added to ActivityFramesTracker during transaction creation`() {
val sut = fixture.getSut()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.sentry.ILogger
import io.sentry.ProfileLifecycle
import io.sentry.SentryLevel
import io.sentry.SentryReplayOptions
import io.sentry.TransactionOptions
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -1091,6 +1092,35 @@ class ManifestMetadataReaderTest {
assertEquals(expectedIdleTimeout.toLong(), fixture.options.idleTimeout)
}

@Test
fun `applyMetadata reads autoTransactionDeadlineTimeoutMillis from metadata`() {
// Arrange
val expectedTimeout = 60000
val bundle = bundleOf(ManifestMetadataReader.DEADLINE_TIMEOUT to expectedTimeout)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertEquals(expectedTimeout.toLong(), fixture.options.deadlineTimeout)
}

@Test
fun `applyMetadata reads autoTransactionDeadlineTimeoutMillis from metadata and keep default value if not found`() {
// Arrange
val context = fixture.getContext()

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertEquals(
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION,
fixture.options.deadlineTimeout,
)
}

@Test
fun `applyMetadata without specifying idleTimeout, stays default`() {
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ constructor(
TransactionOptions().also {
it.isWaitForChildren = true
it.idleTimeout = scopes.options.idleTimeout
it.deadlineTimeout = TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION

// Set deadline timeout based on configured option
val deadlineTimeoutMillis = scopes.options.deadlineTimeout
// No deadline when zero or negative value is set
it.deadlineTimeout = if (deadlineTimeoutMillis <= 0) null else deadlineTimeoutMillis

it.isTrimEnd = true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,48 @@ class SentryNavigationListenerTest {
)
}

@Test
fun `Navigation listener uses custom deadline timeout when set to positive value`() {
val sut = fixture.getSut()
fixture.options.deadlineTimeout = 60000L

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { options -> assertEquals(60000L, options.deadlineTimeout) },
)
}

@Test
fun `Navigation listener uses no deadline timeout when set to zero`() {
val sut = fixture.getSut()
fixture.options.deadlineTimeout = 0L

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { options -> assertNull(options.deadlineTimeout) },
)
}

@Test
fun `Navigation listener uses no deadline timeout when set to negative value`() {
val sut = fixture.getSut()
fixture.options.deadlineTimeout = -1L

sut.onDestinationChanged(fixture.navController, fixture.destination, null)

verify(fixture.scopes)
.startTransaction(
any<TransactionContext>(),
check<TransactionOptions> { options -> assertNull(options.deadlineTimeout) },
)
}

@Test
fun `onDestinationChanged sets scope screen`() {
val sut = fixture.getSut()
Expand Down
2 changes: 2 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3282,6 +3282,7 @@ public class io/sentry/SentryOptions {
public fun getContinuousProfiler ()Lio/sentry/IContinuousProfiler;
public fun getCron ()Lio/sentry/SentryOptions$Cron;
public fun getDateProvider ()Lio/sentry/SentryDateProvider;
public fun getDeadlineTimeout ()J
public fun getDebugMetaLoader ()Lio/sentry/internal/debugmeta/IDebugMetaLoader;
public fun getDefaultScopeType ()Lio/sentry/ScopeType;
public fun getDiagnosticLevel ()Lio/sentry/SentryLevel;
Expand Down Expand Up @@ -3412,6 +3413,7 @@ public class io/sentry/SentryOptions {
public fun setContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
public fun setCron (Lio/sentry/SentryOptions$Cron;)V
public fun setDateProvider (Lio/sentry/SentryDateProvider;)V
public fun setDeadlineTimeout (J)V
public fun setDebug (Z)V
public fun setDebugMetaLoader (Lio/sentry/internal/debugmeta/IDebugMetaLoader;)V
public fun setDefaultScopeType (Lio/sentry/ScopeType;)V
Expand Down
28 changes: 28 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,16 @@ public class SentryOptions {
*/
private boolean startProfilerOnAppStart = false;

/**
* Controls the deadline timeout in milliseconds for automatic transactions. When set to a
* positive value, that value is used as the deadline timeout. When set to a value less than or
* equal to 0, no deadline is applied and transactions will only finish when explicitly finished
* or when the activity lifecycle ends.
*
* <p>Default is 30000 (30 seconds).
*/
private long deadlineTimeout = TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION;

private @NotNull SentryOptions.Logs logs = new SentryOptions.Logs();

private @NotNull ISocketTagger socketTagger = NoOpSocketTagger.getInstance();
Expand Down Expand Up @@ -2020,6 +2030,24 @@ public void setStartProfilerOnAppStart(final boolean startProfilerOnAppStart) {
this.startProfilerOnAppStart = startProfilerOnAppStart;
}

public long getDeadlineTimeout() {
return deadlineTimeout;
}

/**
* Controls the deadline timeout in milliseconds for automatic transactions. When set to a
* positive value, that value is used as the deadline timeout. When set to a value less than or
* equal to 0, no deadline is applied and transactions will only finish when explicitly finished
* or when the activity lifecycle ends.
*
* <p>Default is 30000 (30 seconds).
*
* @param deadlineTimeout the timeout in milliseconds
*/
public void setDeadlineTimeout(long deadlineTimeout) {
this.deadlineTimeout = deadlineTimeout;
}

/**
* Returns the profiling traces dir. path if set
*
Expand Down
27 changes: 27 additions & 0 deletions sentry/src/test/java/io/sentry/SentryOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -815,4 +815,31 @@ class SentryOptionsTest {
options.setTag(null, null)
assertTrue(options.tags.isEmpty())
}

@Test
fun `autoTransactionDeadlineTimeoutMillis option defaults to 30000`() {
val options = SentryOptions.empty()
assertEquals(30000L, options.deadlineTimeout)
}

@Test
fun `autoTransactionDeadlineTimeoutMillis option can be changed`() {
val options = SentryOptions.empty()
options.deadlineTimeout = 60000L
assertEquals(60000L, options.deadlineTimeout)
}

@Test
fun `autoTransactionDeadlineTimeoutMillis option can be set to zero value`() {
val options = SentryOptions.empty()
options.deadlineTimeout = 0L
assertEquals(0L, options.deadlineTimeout)
}

@Test
fun `autoTransactionDeadlineTimeoutMillis option can be set to negative value`() {
val options = SentryOptions.empty()
options.deadlineTimeout = -1L
assertEquals(-1L, options.deadlineTimeout)
}
}
Loading