Skip to content

Commit 2e90ac7

Browse files
authored
Move onFinishCallback before span or transaction is finished (#3459)
* moved span and transaction finish callback before they are actually finished * moved performance collection before transaction is finished, fixing frame measurements not being set
1 parent c17f259 commit 2e90ac7

File tree

6 files changed

+70
-21
lines changed

6 files changed

+70
-21
lines changed

CHANGELOG.md

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

55
### Fixes
66

7+
- Move onFinishCallback before span or transaction is finished ([#3459](https://github.com/getsentry/sentry-java/pull/3459))
78
- Add timestamp when a profile starts ([#3442](https://github.com/getsentry/sentry-java/pull/3442))
89
- Move fragment auto span finish to onFragmentStarted ([#3424](https://github.com/getsentry/sentry-java/pull/3424))
910
- Remove profiling timeout logic and disable profiling on API 21 ([#3478](https://github.com/getsentry/sentry-java/pull/3478))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class ActivityLifecycleIntegrationTest {
303303
sut.ttidSpanMap.values.first().finish()
304304
sut.ttfdSpanMap.values.first().finish()
305305

306-
// then transaction should not be immediatelly finished
306+
// then transaction should not be immediately finished
307307
verify(fixture.hub, never())
308308
.captureTransaction(
309309
anyOrNull(),

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -200,26 +200,45 @@ public void finish(
200200
if (!root.isFinished()
201201
&& (!transactionOptions.isWaitForChildren() || hasAllChildrenFinished())) {
202202

203+
final @NotNull AtomicReference<List<PerformanceCollectionData>> performanceCollectionData =
204+
new AtomicReference<>();
205+
// We set the new spanFinishedCallback here instead of creation time, calling the old one to
206+
// avoid the user overwrites it by setting a custom spanFinishedCallback on the root span
207+
final @Nullable SpanFinishedCallback oldCallback = this.root.getSpanFinishedCallback();
208+
this.root.setSpanFinishedCallback(
209+
span -> {
210+
if (oldCallback != null) {
211+
oldCallback.execute(span);
212+
}
213+
214+
// Let's call the finishCallback here, when the root span has a finished date but it's
215+
// not finished, yet
216+
final @Nullable TransactionFinishedCallback finishedCallback =
217+
transactionOptions.getTransactionFinishedCallback();
218+
if (finishedCallback != null) {
219+
finishedCallback.execute(this);
220+
}
221+
222+
if (transactionPerformanceCollector != null) {
223+
performanceCollectionData.set(transactionPerformanceCollector.stop(this));
224+
}
225+
});
226+
203227
// any un-finished childs will remain unfinished
204228
// as relay takes care of setting the end-timestamp + deadline_exceeded
205229
// see
206230
// https://github.com/getsentry/relay/blob/40697d0a1c54e5e7ad8d183fc7f9543b94fe3839/relay-general/src/store/transactions/processor.rs#L374-L378
207231
root.finish(finishStatus.spanStatus, finishTimestamp);
208232

209-
List<PerformanceCollectionData> performanceCollectionData = null;
210-
if (transactionPerformanceCollector != null) {
211-
performanceCollectionData = transactionPerformanceCollector.stop(this);
212-
}
213-
214233
ProfilingTraceData profilingTraceData = null;
215234
if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) {
216235
profilingTraceData =
217236
hub.getOptions()
218237
.getTransactionProfiler()
219-
.onTransactionFinish(this, performanceCollectionData, hub.getOptions());
238+
.onTransactionFinish(this, performanceCollectionData.get(), hub.getOptions());
220239
}
221-
if (performanceCollectionData != null) {
222-
performanceCollectionData.clear();
240+
if (performanceCollectionData.get() != null) {
241+
performanceCollectionData.get().clear();
223242
}
224243

225244
hub.configureScope(
@@ -232,11 +251,6 @@ public void finish(
232251
});
233252
});
234253
final SentryTransaction transaction = new SentryTransaction(this);
235-
final TransactionFinishedCallback finishedCallback =
236-
transactionOptions.getTransactionFinishedCallback();
237-
if (finishedCallback != null) {
238-
finishedCallback.execute(this);
239-
}
240254

241255
if (timer != null) {
242256
synchronized (timerLock) {
@@ -605,7 +619,9 @@ private boolean hasAllChildrenFinished() {
605619
final List<Span> spans = new ArrayList<>(this.children);
606620
if (!spans.isEmpty()) {
607621
for (final Span span : spans) {
608-
if (!span.isFinished()) {
622+
// This is used in the spanFinishCallback, when the span isn't finished, but has a finish
623+
// date
624+
if (!span.isFinished() && span.getFinishDate() == null) {
609625
return false;
610626
}
611627
}

sentry/src/main/java/io/sentry/Span.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ public final class Span implements ISpan {
3737

3838
private final @NotNull IHub hub;
3939

40-
private final @NotNull AtomicBoolean finished = new AtomicBoolean(false);
40+
private boolean finished = false;
41+
42+
private final @NotNull AtomicBoolean isFinishing = new AtomicBoolean(false);
4143

4244
private final @NotNull SpanOptions options;
4345

@@ -122,7 +124,7 @@ public Span(
122124
final @Nullable SentryDate timestamp,
123125
final @NotNull Instrumenter instrumenter,
124126
@NotNull SpanOptions spanOptions) {
125-
if (finished.get()) {
127+
if (finished) {
126128
return NoOpSpan.getInstance();
127129
}
128130

@@ -133,7 +135,7 @@ public Span(
133135
@Override
134136
public @NotNull ISpan startChild(
135137
final @NotNull String operation, final @Nullable String description) {
136-
if (finished.get()) {
138+
if (finished) {
137139
return NoOpSpan.getInstance();
138140
}
139141

@@ -143,7 +145,7 @@ public Span(
143145
@Override
144146
public @NotNull ISpan startChild(
145147
@NotNull String operation, @Nullable String description, @NotNull SpanOptions spanOptions) {
146-
if (finished.get()) {
148+
if (finished) {
147149
return NoOpSpan.getInstance();
148150
}
149151
return transaction.startChild(context.getSpanId(), operation, description, spanOptions);
@@ -192,7 +194,7 @@ public void finish(@Nullable SpanStatus status) {
192194
@Override
193195
public void finish(final @Nullable SpanStatus status, final @Nullable SentryDate timestamp) {
194196
// the span can be finished only once
195-
if (!finished.compareAndSet(false, true)) {
197+
if (finished || !isFinishing.compareAndSet(false, true)) {
196198
return;
197199
}
198200

@@ -235,6 +237,7 @@ public void finish(final @Nullable SpanStatus status, final @Nullable SentryDate
235237
if (spanFinishedCallback != null) {
236238
spanFinishedCallback.execute(this);
237239
}
240+
finished = true;
238241
}
239242

240243
@Override
@@ -284,7 +287,7 @@ public void setTag(final @NotNull String key, final @NotNull String value) {
284287

285288
@Override
286289
public boolean isFinished() {
287-
return finished.get();
290+
return finished;
288291
}
289292

290293
public @NotNull Map<String, Object> getData() {
@@ -409,6 +412,11 @@ void setSpanFinishedCallback(final @Nullable SpanFinishedCallback callback) {
409412
this.spanFinishedCallback = callback;
410413
}
411414

415+
@Nullable
416+
SpanFinishedCallback getSpanFinishedCallback() {
417+
return spanFinishedCallback;
418+
}
419+
412420
private void updateStartDate(@NotNull SentryDate date) {
413421
this.startTimestamp = date;
414422
}

sentry/src/test/java/io/sentry/SentryTracerTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,4 +1378,15 @@ class SentryTracerTest {
13781378

13791379
assertEquals(5, tracer.children.size)
13801380
}
1381+
1382+
@Test
1383+
fun `tracer is not finished when finishCallback is called`() {
1384+
val transaction = fixture.getSut(transactionFinishedCallback = {
1385+
assertFalse(it.isFinished)
1386+
assertNotNull(it.finishDate)
1387+
})
1388+
assertFalse(transaction.isFinished)
1389+
assertNull(transaction.finishDate)
1390+
transaction.finish()
1391+
}
13811392
}

sentry/src/test/java/io/sentry/SpanTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,19 @@ class SpanTest {
495495
assertSame(span.localMetricsAggregator, span.localMetricsAggregator)
496496
}
497497

498+
// test to ensure that the span is not finished when the finishCallback is called
499+
@Test
500+
fun `span is not finished when finishCallback is called`() {
501+
val span = fixture.getSut()
502+
span.setSpanFinishedCallback {
503+
assertFalse(span.isFinished)
504+
assertNotNull(span.finishDate)
505+
}
506+
assertFalse(span.isFinished)
507+
assertNull(span.finishDate)
508+
span.finish()
509+
}
510+
498511
private fun getTransaction(transactionContext: TransactionContext = TransactionContext("name", "op")): SentryTracer {
499512
return SentryTracer(transactionContext, fixture.hub)
500513
}

0 commit comments

Comments
 (0)