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

Support transaction waiting for children to finish. #1535

Merged
merged 12 commits into from
Jun 23, 2021
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

## Unreleased

* Feat: Automatic breadcrumbs logging for fragment lifecycle (#1522)
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
* Feat: Support transaction waiting for children to finish. (#1535)
* Feat: Capture logged marker in log4j2 and logback appenders (#1551)

## 5.1.0-beta.1

* Feat: Measure app start time (#1487)
* Feat: Automatic breadcrumbs logging for fragment lifecycle (#1522)

## 5.0.1
marandaneto marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public final class io/sentry/Hub : io/sentry/IHub {
public fun startSession ()V
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Z)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;Z)Lio/sentry/ITransaction;
public fun traceHeaders ()Lio/sentry/SentryTraceHeader;
public fun withScope (Lio/sentry/ScopeCallback;)V
}
Expand Down Expand Up @@ -175,6 +176,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub {
public fun startTransaction (Lio/sentry/TransactionContext;)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Z)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;Z)Lio/sentry/ITransaction;
public fun traceHeaders ()Lio/sentry/SentryTraceHeader;
public fun withScope (Lio/sentry/ScopeCallback;)V
}
Expand Down Expand Up @@ -230,6 +232,7 @@ public abstract interface class io/sentry/IHub {
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;)Lio/sentry/ITransaction;
public abstract fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Z)Lio/sentry/ITransaction;
public abstract fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;)Lio/sentry/ITransaction;
public abstract fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;Z)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Z)Lio/sentry/ITransaction;
public fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ITransaction;
public fun startTransaction (Ljava/lang/String;Ljava/lang/String;Lio/sentry/CustomSamplingContext;)Lio/sentry/ITransaction;
Expand Down Expand Up @@ -382,6 +385,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub {
public fun startTransaction (Lio/sentry/TransactionContext;)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Z)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;)Lio/sentry/ITransaction;
public fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;Z)Lio/sentry/ITransaction;
public fun traceHeaders ()Lio/sentry/SentryTraceHeader;
public fun withScope (Lio/sentry/ScopeCallback;)V
}
Expand Down Expand Up @@ -592,6 +596,7 @@ public final class io/sentry/Sentry {
public static fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;)Lio/sentry/ITransaction;
public static fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Z)Lio/sentry/ITransaction;
public static fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;)Lio/sentry/ITransaction;
public static fun startTransaction (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;ZLjava/util/Date;Z)Lio/sentry/ITransaction;
public static fun startTransaction (Lio/sentry/TransactionContext;Z)Lio/sentry/ITransaction;
public static fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ITransaction;
public static fun startTransaction (Ljava/lang/String;Ljava/lang/String;Lio/sentry/CustomSamplingContext;)Lio/sentry/ITransaction;
Expand Down Expand Up @@ -923,6 +928,7 @@ public final class io/sentry/SentryTraceHeader {

public final class io/sentry/SentryTracer : io/sentry/ITransaction {
public fun <init> (Lio/sentry/TransactionContext;Lio/sentry/IHub;)V
public fun <init> (Lio/sentry/TransactionContext;Lio/sentry/IHub;Z)V
public fun finish ()V
public fun finish (Lio/sentry/SpanStatus;)V
public fun getChildren ()Ljava/util/List;
Expand Down
21 changes: 17 additions & 4 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ public void flush(long timeoutMillis) {
final @NotNull TransactionContext transactionContext,
final @Nullable CustomSamplingContext customSamplingContext,
final boolean bindToScope) {
return createTransaction(transactionContext, customSamplingContext, bindToScope, null);
return createTransaction(transactionContext, customSamplingContext, bindToScope, null, false);
}

@ApiStatus.Internal
Expand All @@ -592,14 +592,27 @@ public void flush(long timeoutMillis) {
boolean bindToScope,
@Nullable Date startTimestamp) {
return createTransaction(
transactionContext, customSamplingContext, bindToScope, startTimestamp);
transactionContext, customSamplingContext, bindToScope, startTimestamp, false);
}

@ApiStatus.Internal
@Override
public @NotNull ITransaction startTransaction(
final @NotNull TransactionContext transactionContexts,
final @Nullable CustomSamplingContext customSamplingContext,
final boolean bindToScope,
final @Nullable Date startTimestamp,
final boolean waitForChildren) {
return createTransaction(
transactionContexts, customSamplingContext, bindToScope, startTimestamp, waitForChildren);
}

private @NotNull ITransaction createTransaction(
final @NotNull TransactionContext transactionContext,
final @Nullable CustomSamplingContext customSamplingContext,
final boolean bindToScope,
final @Nullable Date startTimestamp) {
final @Nullable Date startTimestamp,
final boolean waitForChildren) {
Objects.requireNonNull(transactionContext, "transactionContext is required");

ITransaction transaction;
Expand All @@ -622,7 +635,7 @@ public void flush(long timeoutMillis) {
boolean samplingDecision = tracesSampler.sample(samplingContext);
transactionContext.setSampled(samplingDecision);

transaction = new SentryTracer(transactionContext, this, startTimestamp);
transaction = new SentryTracer(transactionContext, this, startTimestamp, waitForChildren);
}
if (bindToScope) {
configureScope(scope -> scope.setTransaction(transaction));
Expand Down
12 changes: 12 additions & 0 deletions sentry/src/main/java/io/sentry/HubAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ public void flush(long timeoutMillis) {
transactionContexts, customSamplingContext, bindToScope, startTimestamp);
}

@ApiStatus.Internal
@Override
public @NotNull ITransaction startTransaction(
@NotNull TransactionContext transactionContexts,
@Nullable CustomSamplingContext customSamplingContext,
boolean bindToScope,
@Nullable Date startTimestamp,
boolean waitForChildren) {
return Sentry.startTransaction(
transactionContexts, customSamplingContext, bindToScope, startTimestamp, waitForChildren);
}

@Override
public @Nullable SentryTraceHeader traceHeaders() {
return Sentry.traceHeaders();
Expand Down
9 changes: 9 additions & 0 deletions sentry/src/main/java/io/sentry/IHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,15 @@ ITransaction startTransaction(
boolean bindToScope,
@Nullable Date startTimestamp);

@ApiStatus.Internal
@NotNull
ITransaction startTransaction(
@NotNull TransactionContext transactionContexts,
@Nullable CustomSamplingContext customSamplingContext,
boolean bindToScope,
@Nullable Date startTimestamp,
boolean waitForChildren);

/**
* Creates a Transaction and returns the instance. Based on the {@link
* SentryOptions#getTracesSampleRate()} the decision if transaction is sampled will be taken by
Expand Down
10 changes: 10 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ public void flush(long timeoutMillis) {}
return NoOpTransaction.getInstance();
}

@Override
public @NotNull ITransaction startTransaction(
@NotNull TransactionContext transactionContexts,
@Nullable CustomSamplingContext customSamplingContext,
boolean bindToScope,
@Nullable Date startTimestamp,
boolean waitForChildren) {
return NoOpTransaction.getInstance();
}

@Override
public @NotNull SentryTraceHeader traceHeaders() {
return new SentryTraceHeader(SentryId.EMPTY_ID, SpanId.EMPTY_ID, true);
Expand Down
16 changes: 16 additions & 0 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,22 @@ public static void endSession() {
.startTransaction(transactionContexts, customSamplingContext, bindToScope, startTimestamp);
}

@ApiStatus.Internal
public static @NotNull ITransaction startTransaction(
final @NotNull TransactionContext transactionContexts,
final @Nullable CustomSamplingContext customSamplingContext,
final boolean bindToScope,
final @Nullable Date startTimestamp,
final boolean waitForChildren) {
return getCurrentHub()
.startTransaction(
transactionContexts,
customSamplingContext,
bindToScope,
startTimestamp,
waitForChildren);
}

/**
* Returns trace header of active transaction or {@code null} if no transaction is active.
*
Expand Down
78 changes: 75 additions & 3 deletions sentry/src/main/java/io/sentry/SentryTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,47 @@ public final class SentryTracer implements ITransaction {
private final @NotNull Contexts contexts = new Contexts();
private @Nullable Request request;
private @NotNull String name;
/**
* When `waitForChildren` is set to `true`, tracer will finish only when both conditions are met
* (the order of meeting condition does not matter): - tracer itself is finished - all child spans
* are finished
*/
private final boolean waitForChildren;

/**
* Holds the status for finished tracer. Tracer can have finishedStatus set, but not be finished
* itself when `waitForChildren` is set to `true`, `#finish()` method was called but there are
* unfinished children spans.
*/
private @NotNull FinishStatus finishStatus = FinishStatus.NOT_FINISHED;

public SentryTracer(final @NotNull TransactionContext context, final @NotNull IHub hub) {
this(context, hub, null);
}

public SentryTracer(
final @NotNull TransactionContext context, final @NotNull IHub hub, boolean waitForChildren) {
this(context, hub, null, waitForChildren);
}

SentryTracer(
final @NotNull TransactionContext context,
final @NotNull IHub hub,
final @Nullable Date startTimestamp) {
this(context, hub, startTimestamp, false);
}

SentryTracer(
final @NotNull TransactionContext context,
final @NotNull IHub hub,
final @Nullable Date startTimestamp,
final boolean waitForChildren) {
Objects.requireNonNull(context, "context is required");
Objects.requireNonNull(hub, "hub is required");
this.root = new Span(context, this, hub, startTimestamp);
this.name = context.getName();
this.hub = hub;
this.waitForChildren = waitForChildren;
}

public @NotNull List<Span> getChildren() {
Expand Down Expand Up @@ -97,7 +124,19 @@ private ISpan createChild(
Objects.requireNonNull(parentSpanId, "parentSpanId is required");
Objects.requireNonNull(operation, "operation is required");
final Span span =
new Span(root.getTraceId(), parentSpanId, this, operation, this.hub, timestamp);
new Span(
root.getTraceId(),
parentSpanId,
this,
operation,
this.hub,
timestamp,
__ -> {
final FinishStatus finishStatus = this.finishStatus;
if (finishStatus.isFinishing) {
finish(finishStatus.spanStatus);
}
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
});
span.setDescription(description);
this.children.add(span);
return span;
Expand Down Expand Up @@ -150,8 +189,9 @@ public void finish() {

@Override
public void finish(@Nullable SpanStatus status) {
if (!root.isFinished()) {
root.finish(status);
this.finishStatus = FinishStatus.finishing(status);
if (!root.isFinished() && (!waitForChildren || hasAllChildrenFinished())) {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
root.finish(finishStatus.spanStatus);
hub.configureScope(
scope -> {
scope.withTransaction(
Expand All @@ -166,6 +206,18 @@ public void finish(@Nullable SpanStatus status) {
}
}

private boolean hasAllChildrenFinished() {
final List<Span> spans = new ArrayList<>(this.children);
if (!spans.isEmpty()) {
for (final Span span : spans) {
if (!span.isFinished()) {
return false;
}
}
}
return true;
}

@Override
public void setOperation(final @NotNull String operation) {
this.root.setOperation(operation);
Expand Down Expand Up @@ -289,4 +341,24 @@ public void setRequest(final @Nullable Request request) {
Span getRoot() {
return root;
}

private static final class FinishStatus {
static final FinishStatus NOT_FINISHED = FinishStatus.notFinished();

private final boolean isFinishing;
private final @Nullable SpanStatus spanStatus;

static @NotNull FinishStatus finishing(final @Nullable SpanStatus finishStatus) {
return new FinishStatus(true, finishStatus);
}

private static @NotNull FinishStatus notFinished() {
return new FinishStatus(false, null);
}

private FinishStatus(final boolean isFinishing, final @Nullable SpanStatus spanStatus) {
this.isFinishing = isFinishing;
this.spanStatus = spanStatus;
}
}
}
12 changes: 10 additions & 2 deletions sentry/src/main/java/io/sentry/Span.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ public final class Span implements ISpan {

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

private final @Nullable SpanListener spanListener;

Span(
final @NotNull SentryId traceId,
final @Nullable SpanId parentSpanId,
final @NotNull SentryTracer transaction,
final @NotNull String operation,
final @NotNull IHub hub) {
this(traceId, parentSpanId, transaction, operation, hub, null);
this(traceId, parentSpanId, transaction, operation, hub, null, null);
}

Span(
Expand All @@ -48,12 +50,14 @@ public final class Span implements ISpan {
final @NotNull SentryTracer transaction,
final @NotNull String operation,
final @NotNull IHub hub,
final @Nullable Date startTimestamp) {
final @Nullable Date startTimestamp,
final @Nullable SpanListener spanListener) {
this.context =
new SpanContext(traceId, new SpanId(), operation, parentSpanId, transaction.isSampled());
this.transaction = Objects.requireNonNull(transaction, "transaction is required");
this.startTimestamp = startTimestamp != null ? startTimestamp : DateUtils.getCurrentDateTime();
this.hub = Objects.requireNonNull(hub, "hub is required");
this.spanListener = spanListener;
}

@VisibleForTesting
Expand All @@ -66,6 +70,7 @@ public Span(
this.transaction = Objects.requireNonNull(sentryTracer, "sentryTracer is required");
this.hub = Objects.requireNonNull(hub, "hub is required");
this.startTimestamp = startTimestamp != null ? startTimestamp : DateUtils.getCurrentDateTime();
this.spanListener = null;
}

public @NotNull Date getStartTimestamp() {
Expand Down Expand Up @@ -117,6 +122,9 @@ public void finish(@Nullable SpanStatus status) {
if (throwable != null) {
hub.setSpanContext(throwable, this, this.transaction.getName());
}
if (spanListener != null) {
spanListener.onSpanFinished(this);
}
}

@Override
Expand Down
12 changes: 12 additions & 0 deletions sentry/src/main/java/io/sentry/SpanListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.sentry;

import org.jetbrains.annotations.NotNull;

interface SpanListener {
/**
* Called when observed span finishes.
*
* @param span the span that has finished.
*/
void onSpanFinished(final @NotNull Span span);
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
}
Loading