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

Move onFinishCallback before span or transaction is finished #3459

Merged
merged 7 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Move onFinishCallback before span or transaction is finished ([#3459](https://github.com/getsentry/sentry-java/pull/3459))
- Add timestamp when a profile starts ([#3442](https://github.com/getsentry/sentry-java/pull/3442))
- Move fragment auto span finish to onFragmentStarted ([#3424](https://github.com/getsentry/sentry-java/pull/3424))
- Remove profiling timeout logic and disable profiling on API 21 ([#3478](https://github.com/getsentry/sentry-java/pull/3478))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ class ActivityLifecycleIntegrationTest {
sut.ttidSpanMap.values.first().finish()
sut.ttfdSpanMap.values.first().finish()

// then transaction should not be immediatelly finished
// then transaction should not be immediately finished
verify(fixture.hub, never())
.captureTransaction(
anyOrNull(),
Expand Down
44 changes: 30 additions & 14 deletions sentry/src/main/java/io/sentry/SentryTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,26 +200,45 @@ public void finish(
if (!root.isFinished()
&& (!transactionOptions.isWaitForChildren() || hasAllChildrenFinished())) {

final @NotNull AtomicReference<List<PerformanceCollectionData>> performanceCollectionData =
new AtomicReference<>();
// We set the new spanFinishedCallback here instead of creation time, calling the old one to
// avoid the user overwrites it by setting a custom spanFinishedCallback on the root span
final @Nullable SpanFinishedCallback oldCallback = this.root.getSpanFinishedCallback();
this.root.setSpanFinishedCallback(
span -> {
if (oldCallback != null) {
oldCallback.execute(span);
}

// Let's call the finishCallback here, when the root span has a finished date but it's
// not finished, yet
final @Nullable TransactionFinishedCallback finishedCallback =
transactionOptions.getTransactionFinishedCallback();
if (finishedCallback != null) {
finishedCallback.execute(this);
}
Copy link
Member Author

@stefanosiano stefanosiano Jun 5, 2024

Choose a reason for hiding this comment

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

I moved the performanceCollectors here, to fix an issue with SpanFrameMetricsCollection which was not setting the frame counts measurements, due to transaction being already finished


if (transactionPerformanceCollector != null) {
performanceCollectionData.set(transactionPerformanceCollector.stop(this));
}
});

// any un-finished childs will remain unfinished
// as relay takes care of setting the end-timestamp + deadline_exceeded
// see
// https://github.com/getsentry/relay/blob/40697d0a1c54e5e7ad8d183fc7f9543b94fe3839/relay-general/src/store/transactions/processor.rs#L374-L378
root.finish(finishStatus.spanStatus, finishTimestamp);

List<PerformanceCollectionData> performanceCollectionData = null;
if (transactionPerformanceCollector != null) {
performanceCollectionData = transactionPerformanceCollector.stop(this);
}

ProfilingTraceData profilingTraceData = null;
if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) {
profilingTraceData =
hub.getOptions()
.getTransactionProfiler()
.onTransactionFinish(this, performanceCollectionData, hub.getOptions());
.onTransactionFinish(this, performanceCollectionData.get(), hub.getOptions());
}
if (performanceCollectionData != null) {
performanceCollectionData.clear();
if (performanceCollectionData.get() != null) {
performanceCollectionData.get().clear();
}

hub.configureScope(
Expand All @@ -232,11 +251,6 @@ public void finish(
});
});
final SentryTransaction transaction = new SentryTransaction(this);
final TransactionFinishedCallback finishedCallback =
transactionOptions.getTransactionFinishedCallback();
if (finishedCallback != null) {
finishedCallback.execute(this);
}

if (timer != null) {
synchronized (timerLock) {
Expand Down Expand Up @@ -605,7 +619,9 @@ private boolean hasAllChildrenFinished() {
final List<Span> spans = new ArrayList<>(this.children);
if (!spans.isEmpty()) {
for (final Span span : spans) {
if (!span.isFinished()) {
// This is used in the spanFinishCallback, when the span isn't finished, but has a finish
// date
if (!span.isFinished() && span.getFinishDate() == null) {
return false;
}
}
Expand Down
20 changes: 14 additions & 6 deletions sentry/src/main/java/io/sentry/Span.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ public final class Span implements ISpan {

private final @NotNull IHub hub;

private final @NotNull AtomicBoolean finished = new AtomicBoolean(false);
private boolean finished = false;
Copy link
Member

Choose a reason for hiding this comment

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

nit this probably should be volatile, but then on the other hand we also don't do this for other span properties ¯_(ツ)_/¯

Copy link
Member Author

Choose a reason for hiding this comment

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

let's keep AtomicBoolean and change it later if ever needed, as i don't think it really changes anything


private final @NotNull AtomicBoolean isFinishing = new AtomicBoolean(false);

private final @NotNull SpanOptions options;

Expand Down Expand Up @@ -122,7 +124,7 @@ public Span(
final @Nullable SentryDate timestamp,
final @NotNull Instrumenter instrumenter,
@NotNull SpanOptions spanOptions) {
if (finished.get()) {
if (finished) {
return NoOpSpan.getInstance();
}

Expand All @@ -133,7 +135,7 @@ public Span(
@Override
public @NotNull ISpan startChild(
final @NotNull String operation, final @Nullable String description) {
if (finished.get()) {
if (finished) {
return NoOpSpan.getInstance();
}

Expand All @@ -143,7 +145,7 @@ public Span(
@Override
public @NotNull ISpan startChild(
@NotNull String operation, @Nullable String description, @NotNull SpanOptions spanOptions) {
if (finished.get()) {
if (finished) {
return NoOpSpan.getInstance();
}
return transaction.startChild(context.getSpanId(), operation, description, spanOptions);
Expand Down Expand Up @@ -192,7 +194,7 @@ public void finish(@Nullable SpanStatus status) {
@Override
public void finish(final @Nullable SpanStatus status, final @Nullable SentryDate timestamp) {
// the span can be finished only once
if (!finished.compareAndSet(false, true)) {
if (finished || !isFinishing.compareAndSet(false, true)) {
return;
}

Expand Down Expand Up @@ -235,6 +237,7 @@ public void finish(final @Nullable SpanStatus status, final @Nullable SentryDate
if (spanFinishedCallback != null) {
spanFinishedCallback.execute(this);
}
finished = true;
}

@Override
Expand Down Expand Up @@ -284,7 +287,7 @@ public void setTag(final @NotNull String key, final @NotNull String value) {

@Override
public boolean isFinished() {
return finished.get();
return finished;
}

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

@Nullable
SpanFinishedCallback getSpanFinishedCallback() {
return spanFinishedCallback;
}

private void updateStartDate(@NotNull SentryDate date) {
this.startTimestamp = date;
}
Expand Down
11 changes: 11 additions & 0 deletions sentry/src/test/java/io/sentry/SentryTracerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1378,4 +1378,15 @@ class SentryTracerTest {

assertEquals(5, tracer.children.size)
}

@Test
fun `tracer is not finished when finishCallback is called`() {
val transaction = fixture.getSut(transactionFinishedCallback = {
assertFalse(it.isFinished)
assertNotNull(it.finishDate)
})
assertFalse(transaction.isFinished)
assertNull(transaction.finishDate)
transaction.finish()
}
}
13 changes: 13 additions & 0 deletions sentry/src/test/java/io/sentry/SpanTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,19 @@ class SpanTest {
assertSame(span.localMetricsAggregator, span.localMetricsAggregator)
}

// test to ensure that the span is not finished when the finishCallback is called
@Test
fun `span is not finished when finishCallback is called`() {
val span = fixture.getSut()
span.setSpanFinishedCallback {
assertFalse(span.isFinished)
assertNotNull(span.finishDate)
}
assertFalse(span.isFinished)
assertNull(span.finishDate)
span.finish()
}

private fun getTransaction(transactionContext: TransactionContext = TransactionContext("name", "op")): SentryTracer {
return SentryTracer(transactionContext, fixture.hub)
}
Expand Down
Loading