Skip to content

Commit 6dd3b53

Browse files
authored
Merge branch 'feat/7.0.0' into feat/get-span-root-span
2 parents 348fb56 + 0b3de21 commit 6dd3b53

File tree

13 files changed

+279
-51
lines changed

13 files changed

+279
-51
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Breaking changes:
99
- Reduce flush timeout to 4s on Android to avoid ANRs ([#2858](https://github.com/getsentry/sentry-java/pull/2858))
1010
- Set ip_address to {{auto}} by default, even if sendDefaultPII is disabled ([#2860](https://github.com/getsentry/sentry-java/pull/2860))
1111
- Instead use the "Prevent Storing of IP Addresses" option in the "Security & Privacy" project settings on sentry.io
12+
- Reduce timeout of AsyncHttpTransport to avoid ANR ([#2879](https://github.com/getsentry/sentry-java/pull/2879))
13+
- Add deadline timeout for automatic transactions ([#2865](https://github.com/getsentry/sentry-java/pull/2865))
14+
- This affects all automatically generated transactions on Android (UI, clicks), the default timeout is 30s
1215
- If global hub mode is enabled (default on Android), Sentry.getSpan() returns the root span instead of the latest span ([#2855](https://github.com/getsentry/sentry-java/pull/2855))
1316

1417
### Fixes

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ private void startTracing(final @NotNull Activity activity) {
192192
final Boolean coldStart = AppStartState.getInstance().isColdStart();
193193

194194
final TransactionOptions transactionOptions = new TransactionOptions();
195+
transactionOptions.setDeadlineTimeout(
196+
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
197+
195198
if (options.isEnableActivityLifecycleTracingAutoFinish()) {
196199
transactionOptions.setIdleTimeout(options.getIdleTimeout());
197200
transactionOptions.setTrimEnd(true);

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ private void startTracing(final @NotNull UiElement target, final @NotNull String
237237

238238
final TransactionOptions transactionOptions = new TransactionOptions();
239239
transactionOptions.setWaitForChildren(true);
240+
transactionOptions.setDeadlineTimeout(
241+
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
240242
transactionOptions.setIdleTimeout(options.getIdleTimeout());
241243
transactionOptions.setTrimEnd(true);
242244

sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import org.mockito.kotlin.clearInvocations
4343
import org.mockito.kotlin.eq
4444
import org.mockito.kotlin.mock
4545
import org.mockito.kotlin.never
46-
import org.mockito.kotlin.times
4746
import org.mockito.kotlin.verify
4847
import org.mockito.kotlin.whenever
4948
import org.robolectric.Shadows.shadowOf
@@ -354,7 +353,7 @@ class ActivityLifecycleIntegrationTest {
354353
}
355354

356355
@Test
357-
fun `Transaction op is ui_load`() {
356+
fun `Transaction op is ui_load and idle+deadline timeouts are set`() {
358357
val sut = fixture.getSut()
359358
fixture.options.tracesSampleRate = 1.0
360359
sut.register(fixture.hub, fixture.options)
@@ -365,11 +364,14 @@ class ActivityLifecycleIntegrationTest {
365364
sut.onActivityCreated(activity, fixture.bundle)
366365

367366
verify(fixture.hub).startTransaction(
368-
check {
367+
check<TransactionContext> {
369368
assertEquals("ui.load", it.operation)
370369
assertEquals(TransactionNameSource.COMPONENT, it.transactionNameSource)
371370
},
372-
any<TransactionOptions>()
371+
check<TransactionOptions> { transactionOptions ->
372+
assertEquals(fixture.options.idleTimeout, transactionOptions.idleTimeout)
373+
assertEquals(TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION, transactionOptions.deadlineTimeout)
374+
}
373375
)
374376
}
375377

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,24 @@ class SentryGestureListenerTracingTest {
206206
)
207207
}
208208

209+
@Test
210+
fun `captures transaction and both idle+deadline timeouts are set`() {
211+
val sut = fixture.getSut<View>()
212+
213+
sut.onSingleTapUp(fixture.event)
214+
215+
verify(fixture.hub).startTransaction(
216+
any<TransactionContext>(),
217+
check<TransactionOptions> { transactionOptions ->
218+
assertEquals(fixture.options.idleTimeout, transactionOptions.idleTimeout)
219+
assertEquals(
220+
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION,
221+
transactionOptions.deadlineTimeout
222+
)
223+
}
224+
)
225+
}
226+
209227
@Test
210228
fun `captures transaction with interaction event type as op`() {
211229
val sut = fixture.getSut<View>()

sentry-android-navigation/src/main/java/io/sentry/android/navigation/SentryNavigationListener.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,16 @@ class SentryNavigationListener @JvmOverloads constructor(
132132
// we add '/' to the name to match dart and web pattern
133133
name = "/" + name.substringBefore('/') // strip out arguments from the tx name
134134

135-
val transactonOptions = TransactionOptions().also {
135+
val transactionOptions = TransactionOptions().also {
136136
it.isWaitForChildren = true
137137
it.idleTimeout = hub.options.idleTimeout
138+
it.deadlineTimeout = TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION
138139
it.isTrimEnd = true
139140
}
140141

141142
val transaction = hub.startTransaction(
142143
TransactionContext(name, TransactionNameSource.ROUTE, NAVIGATION_OP),
143-
transactonOptions
144+
transactionOptions
144145
)
145146

146147
transaction.spanContext.origin = traceOriginAppendix?.let {

sentry-android-navigation/src/test/java/io/sentry/android/navigation/SentryNavigationListenerTest.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,12 @@ class SentryNavigationListenerTest {
9494
whenever(context.resources).thenReturn(resources)
9595
whenever(navController.context).thenReturn(context)
9696
whenever(destination.route).thenReturn(toRoute)
97-
return SentryNavigationListener(hub, enableBreadcrumbs, enableTracing, traceOriginAppendix)
97+
return SentryNavigationListener(
98+
hub,
99+
enableBreadcrumbs,
100+
enableTracing,
101+
traceOriginAppendix
102+
)
98103
}
99104
}
100105

@@ -355,7 +360,8 @@ class SentryNavigationListenerTest {
355360
fun `starts new trace if performance is disabled`() {
356361
val sut = fixture.getSut(enableTracing = false)
357362

358-
val argumentCaptor: ArgumentCaptor<ScopeCallback> = ArgumentCaptor.forClass(ScopeCallback::class.java)
363+
val argumentCaptor: ArgumentCaptor<ScopeCallback> =
364+
ArgumentCaptor.forClass(ScopeCallback::class.java)
359365
val scope = Scope(fixture.options)
360366
val propagationContextAtStart = scope.propagationContext
361367
whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer {
@@ -385,4 +391,18 @@ class SentryNavigationListenerTest {
385391

386392
assertEquals("auto.navigation.jetpack_compose", fixture.transaction.spanContext.origin)
387393
}
394+
395+
@Test
396+
fun `Navigation listener transactions set automatic deadline timeout`() {
397+
val sut = fixture.getSut()
398+
399+
sut.onDestinationChanged(fixture.navController, fixture.destination, null)
400+
401+
verify(fixture.hub).startTransaction(
402+
any<TransactionContext>(),
403+
check<TransactionOptions> { options ->
404+
assertEquals(TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION, options.deadlineTimeout)
405+
}
406+
)
407+
}
388408
}

sentry/api/sentry.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,15 +2356,18 @@ public abstract interface class io/sentry/TransactionFinishedCallback {
23562356
}
23572357

23582358
public final class io/sentry/TransactionOptions : io/sentry/SpanOptions {
2359+
public static final field DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION J
23592360
public fun <init> ()V
23602361
public fun getCustomSamplingContext ()Lio/sentry/CustomSamplingContext;
2362+
public fun getDeadlineTimeout ()Ljava/lang/Long;
23612363
public fun getIdleTimeout ()Ljava/lang/Long;
23622364
public fun getStartTimestamp ()Lio/sentry/SentryDate;
23632365
public fun getTransactionFinishedCallback ()Lio/sentry/TransactionFinishedCallback;
23642366
public fun isBindToScope ()Z
23652367
public fun isWaitForChildren ()Z
23662368
public fun setBindToScope (Z)V
23672369
public fun setCustomSamplingContext (Lio/sentry/CustomSamplingContext;)V
2370+
public fun setDeadlineTimeout (Ljava/lang/Long;)V
23682371
public fun setIdleTimeout (Ljava/lang/Long;)V
23692372
public fun setStartTimestamp (Lio/sentry/SentryDate;)V
23702373
public fun setTransactionFinishedCallback (Lio/sentry/TransactionFinishedCallback;)V

sentry/src/main/java/io/sentry/SentryTracer.java

Lines changed: 106 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ public final class SentryTracer implements ITransaction {
3737
*/
3838
private @NotNull FinishStatus finishStatus = FinishStatus.NOT_FINISHED;
3939

40-
private volatile @Nullable TimerTask timerTask;
40+
private volatile @Nullable TimerTask idleTimeoutTask;
41+
private volatile @Nullable TimerTask deadlineTimeoutTask;
42+
4143
private volatile @Nullable Timer timer = null;
4244
private final @NotNull Object timerLock = new Object();
43-
private final @NotNull AtomicBoolean isFinishTimerRunning = new AtomicBoolean(false);
45+
46+
private final @NotNull AtomicBoolean isIdleFinishTimerRunning = new AtomicBoolean(false);
47+
private final @NotNull AtomicBoolean isDeadlineTimerRunning = new AtomicBoolean(false);
4448

4549
private final @NotNull Baggage baggage;
4650
private @NotNull TransactionNameSource transactionNameSource;
@@ -92,43 +96,59 @@ public SentryTracer(
9296
transactionPerformanceCollector.start(this);
9397
}
9498

95-
if (transactionOptions.getIdleTimeout() != null) {
99+
if (transactionOptions.getIdleTimeout() != null
100+
|| transactionOptions.getDeadlineTimeout() != null) {
96101
timer = new Timer(true);
102+
103+
scheduleDeadlineTimeout();
97104
scheduleFinish();
98105
}
99106
}
100107

101108
@Override
102109
public void scheduleFinish() {
103110
synchronized (timerLock) {
104-
cancelTimer();
105111
if (timer != null) {
106-
isFinishTimerRunning.set(true);
107-
timerTask =
108-
new TimerTask() {
109-
@Override
110-
public void run() {
111-
finishFromTimer();
112-
}
113-
};
114-
115-
try {
116-
timer.schedule(timerTask, transactionOptions.getIdleTimeout());
117-
} catch (Throwable e) {
118-
hub.getOptions()
119-
.getLogger()
120-
.log(SentryLevel.WARNING, "Failed to schedule finish timer", e);
121-
// if we failed to schedule the finish timer for some reason, we finish it here right away
122-
finishFromTimer();
112+
final @Nullable Long idleTimeout = transactionOptions.getIdleTimeout();
113+
114+
if (idleTimeout != null) {
115+
cancelIdleTimer();
116+
isIdleFinishTimerRunning.set(true);
117+
idleTimeoutTask =
118+
new TimerTask() {
119+
@Override
120+
public void run() {
121+
onIdleTimeoutReached();
122+
}
123+
};
124+
125+
try {
126+
timer.schedule(idleTimeoutTask, idleTimeout);
127+
} catch (Throwable e) {
128+
hub.getOptions()
129+
.getLogger()
130+
.log(SentryLevel.WARNING, "Failed to schedule finish timer", e);
131+
// if we failed to schedule the finish timer for some reason, we finish it here right
132+
// away
133+
onIdleTimeoutReached();
134+
}
123135
}
124136
}
125137
}
126138
}
127139

128-
private void finishFromTimer() {
129-
final SpanStatus status = getStatus();
140+
private void onIdleTimeoutReached() {
141+
final @Nullable SpanStatus status = getStatus();
130142
finish((status != null) ? status : SpanStatus.OK);
131-
isFinishTimerRunning.set(false);
143+
isIdleFinishTimerRunning.set(false);
144+
}
145+
146+
private void onDeadlineTimeoutReached() {
147+
final @Nullable SpanStatus status = getStatus();
148+
forceFinish(
149+
(status != null) ? status : SpanStatus.DEADLINE_EXCEEDED,
150+
transactionOptions.getIdleTimeout() != null);
151+
isDeadlineTimerRunning.set(false);
132152
}
133153

134154
@Override
@@ -222,6 +242,8 @@ public void finish(
222242
if (timer != null) {
223243
synchronized (timerLock) {
224244
if (timer != null) {
245+
cancelIdleTimer();
246+
cancelDeadlineTimer();
225247
timer.cancel();
226248
timer = null;
227249
}
@@ -244,12 +266,51 @@ public void finish(
244266
}
245267
}
246268

247-
private void cancelTimer() {
269+
private void cancelIdleTimer() {
248270
synchronized (timerLock) {
249-
if (timerTask != null) {
250-
timerTask.cancel();
251-
isFinishTimerRunning.set(false);
252-
timerTask = null;
271+
if (idleTimeoutTask != null) {
272+
idleTimeoutTask.cancel();
273+
isIdleFinishTimerRunning.set(false);
274+
idleTimeoutTask = null;
275+
}
276+
}
277+
}
278+
279+
private void scheduleDeadlineTimeout() {
280+
final @Nullable Long deadlineTimeOut = transactionOptions.getDeadlineTimeout();
281+
if (deadlineTimeOut != null) {
282+
synchronized (timerLock) {
283+
if (timer != null) {
284+
cancelDeadlineTimer();
285+
isDeadlineTimerRunning.set(true);
286+
deadlineTimeoutTask =
287+
new TimerTask() {
288+
@Override
289+
public void run() {
290+
onDeadlineTimeoutReached();
291+
}
292+
};
293+
try {
294+
timer.schedule(deadlineTimeoutTask, deadlineTimeOut);
295+
} catch (Throwable e) {
296+
hub.getOptions()
297+
.getLogger()
298+
.log(SentryLevel.WARNING, "Failed to schedule finish timer", e);
299+
// if we failed to schedule the finish timer for some reason, we finish it here right
300+
// away
301+
onDeadlineTimeoutReached();
302+
}
303+
}
304+
}
305+
}
306+
}
307+
308+
private void cancelDeadlineTimer() {
309+
synchronized (timerLock) {
310+
if (deadlineTimeoutTask != null) {
311+
deadlineTimeoutTask.cancel();
312+
isDeadlineTimerRunning.set(false);
313+
deadlineTimeoutTask = null;
253314
}
254315
}
255316
}
@@ -360,7 +421,7 @@ private ISpan createChild(
360421

361422
Objects.requireNonNull(parentSpanId, "parentSpanId is required");
362423
Objects.requireNonNull(operation, "operation is required");
363-
cancelTimer();
424+
cancelIdleTimer();
364425
final Span span =
365426
new Span(
366427
root.getTraceId(),
@@ -720,8 +781,14 @@ Span getRoot() {
720781

721782
@TestOnly
722783
@Nullable
723-
TimerTask getTimerTask() {
724-
return timerTask;
784+
TimerTask getIdleTimeoutTask() {
785+
return idleTimeoutTask;
786+
}
787+
788+
@TestOnly
789+
@Nullable
790+
TimerTask getDeadlineTimeoutTask() {
791+
return deadlineTimeoutTask;
725792
}
726793

727794
@TestOnly
@@ -733,7 +800,13 @@ Timer getTimer() {
733800
@TestOnly
734801
@NotNull
735802
AtomicBoolean isFinishTimerRunning() {
736-
return isFinishTimerRunning;
803+
return isIdleFinishTimerRunning;
804+
}
805+
806+
@TestOnly
807+
@NotNull
808+
AtomicBoolean isDeadlineTimerRunning() {
809+
return isDeadlineTimerRunning;
737810
}
738811

739812
@TestOnly

0 commit comments

Comments
 (0)