Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

RFC / WIP: make "auto-finishing" optional and non-Tracer-specific #166

Closed
wants to merge 8 commits into from
Closed
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The common case starts an `ActiveSpan` that's automatically registered for intra
```java
io.opentracing.Tracer tracer = ...;
...
try (ActiveSpan activeSpan = tracer.buildSpan("someWork").startActive()) {
try (ActiveSpan activeSpan = tracer.buildSpan("someWork").startActive(new AutoFinisher())) {
Copy link
Member

Choose a reason for hiding this comment

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

it seems the try() would still call close() and finish the span, whether the AutoFinisher is passed or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I reverted to the old BaseSpan/Span/ActiveSpan class hierarchy)

// Do things.
//
// If we create async work, `activeSpan.capture()` allows us to pass the `ActiveSpan` along as well.
Expand All @@ -68,7 +68,7 @@ The above is semantically equivalent to the more explicit try-finally version:
```java
io.opentracing.Tracer tracer = ...;
...
ActiveSpan activeSpan = tracer.buildSpan("someWork").startActive();
ActiveSpan activeSpan = tracer.buildSpan("someWork").startActive(new AutoFinisher());
Copy link
Contributor

Choose a reason for hiding this comment

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

Opinion: I prefer no ActiveSpan type vs adding more options to it. This is generally discussed in #80 so not repeating here

try {
// Do things.
} finally {
Expand Down
45 changes: 45 additions & 0 deletions opentracing-api/src/main/java/io/opentracing/ActiveSpan.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,42 @@
* {@link ActiveSpan.Continuation}s, then re-{@link Continuation#activate()}d later.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this PR tries to distance from refcounting. There's a line that still talks about that which might be something to remove in this PR https://github.com/bhs/opentracing-java/blob/91cf7e5e500f635b9b765df3a107e0de32ad5f0e/opentracing-api/src/main/java/io/opentracing/ActiveSpan.java#L108

*
* <p>
* Propagated {@link ActiveSpan} instances form a tree within a process, where each node is an {@link ActiveSpan} and
* each edge is a {@link ActiveSpan.Continuation}. The {@link ActiveSpan.Observer} interface provides visibility into
* the node/edge creations as well as {@link ActiveSpan} activation/deactivation lifecycles.
*
* <p>
* Below is an illustration of how ActiveSpan and Continuation instances form a tree.
* <pre>
*
* ActiveSpan
* |
* +- .capture() -> Continuation
* | |
* | +- .activate() -> ActiveSpan
* | |
* | +- .capture() -> Continuation
* | | |
* | | +- .activate() -> ActiveSpan
* | | |
* | | +- .deactivate();
* | +- .capture() -> Continuation
* | |
* | +- .activate() -> ActiveSpan
* | |
* | +- .deactivate();
* |
* +- .capture() -> Continuation
* | |
* | +- .activate() -> ActiveSpan
* | |
* | +- .deactivate();
* |
* +- .deactivate();
*
* </pre>
*
* <p>
* NOTE: {@link ActiveSpan} extends {@link Closeable} rather than {@code AutoCloseable} in order to preserve support
* for JDK1.6.
*
Expand Down Expand Up @@ -106,4 +142,13 @@ interface Continuation {
ActiveSpan activate();
}

/**
* The {@link ActiveSpan.Observer} interface learns of capture, activate, and deactivate calls for a given
* {@link ActiveSpan} instance and any of its "descendants" in the capture/activate tree.
*/
interface Observer {
void afterCapture(ActiveSpan captured, Continuation destination);
Copy link
Member

Choose a reason for hiding this comment

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

These methods look a bit fancy. Isn't it better to just pass the span in all of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess I didn't want to leave anything out that would be "on hand" in an implementation. Agree that this smells funny, though.

void afterActivate(Continuation source, ActiveSpan justActivated);
void afterDeactivate(ActiveSpan deactivated, Finishable finisher);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
public interface ActiveSpanSource {

/**
* Return the {@link ActiveSpan active span}. This does not affect the internal reference count for the
* {@link ActiveSpan}.
* Return the {@link ActiveSpan active span}.
*
* <p>
* If there is an {@link ActiveSpan active span}, it becomes an implicit parent of any newly-created
Expand All @@ -38,13 +37,19 @@ public interface ActiveSpanSource {
*/
ActiveSpan activeSpan();

/**
* @see #makeActive(Span, ActiveSpan.Observer)
Copy link
Member

Choose a reason for hiding this comment

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

ambiguous - is it a shortcut for makeActive(span, null) or something else? Or perhaps implementation-dependent.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to maintain backward compatibility of behaviour, then I think the javadoc comment for all of the methods that have an Observer variant should say that they represent the case of using the AutoFinisher observer, e.g. SpanBuilder.startActive(), ActiveSpanSource.makeActive(Span), Tracer.makeActive(Span).

We may also want to consider deprecating these methods, so that for the 1.0 API the Observer has to explicitly be specified, e.g. whether null or AutoFinisher.

*/
ActiveSpan makeActive(Span span);
/**
* Wrap and "make active" a {@link Span} by encapsulating it – and any active state (e.g., MDC state) in the
* current thread – in a new {@link ActiveSpan}.
*
* @param span the Span to wrap in an {@link ActiveSpan}
* @param observer the observer that learns of activations, deactivations, and captures related to the returned
* {@link ActiveSpan}. May be null (which implies "no observer")
* @return an {@link ActiveSpan} that encapsulates the given {@link Span} and any other
* {@link ActiveSpanSource}-specific context (e.g., the MDC context map)
*/
ActiveSpan makeActive(Span span);
ActiveSpan makeActive(Span span, ActiveSpan.Observer observer);

Choose a reason for hiding this comment

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

One idea (not saying it's a good one... or that I understand all the implications) for making this be more backwards compatible is to make the Observer argument be a var arg. This would have the additional side effect of potentially allowing multiple Observers.

}
32 changes: 32 additions & 0 deletions opentracing-api/src/main/java/io/opentracing/Finishable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.opentracing;

/**
* {@link Finishable} factors out the overloaded {@link #finish()} method(s).
*
* @see ActiveSpan.Observer#onDeactivate(ActiveSpan, Finishable)
Copy link
Contributor

Choose a reason for hiding this comment

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

afterDeactivate

* @see Span
*/
public interface Finishable {
/**
* Sets the end timestamp to now and records the span.
*
* <p>With the exception of calls to {@link BaseSpan#context}, this should be the last call made to the span
* instance. Future calls to {@link #finish} are defined as noops, and future calls to methods other than
* {@link BaseSpan#context} lead to undefined behavior.
*
* @see BaseSpan#context()
*/
void finish();

/**
* Sets an explicit end timestamp and records the span.
*
* <p>With the exception of calls to Span.context(), this should be the last call made to the span instance, and to
* do otherwise leads to undefined behavior.
*
* @param finishMicros an explicit finish time, in microseconds since the epoch
*
* @see BaseSpan#context()
*/
void finish(long finishMicros);

Choose a reason for hiding this comment

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

Would this be a good time to consider separating the units from the value? Perhaps a signature like this:
void finish(long finishTime, TimeUnit units)
I feel having implied unit magnitude often results in confusion. In the past, I've had to write code that dynamically figures out the proper magnitude assuming an incorrectly reported scale (seconds vs milliseconds) all because the api was used incorrectly.

}
33 changes: 9 additions & 24 deletions opentracing-api/src/main/java/io/opentracing/Span.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,14 @@
* <p>{@link Span}s are created by the {@link Tracer.SpanBuilder#startManual} method; see {@link ActiveSpan} for
* a {@link BaseSpan} extension designed for automatic in-process propagation.
*
* @see ActiveSpan for automatic propagation (recommended for most intstrumentation!)
* <p>
* Note that most application code interacts with {@link ActiveSpan} instances (which make themselves available
* for in-process propagation via the {@link ActiveSpanSource} interface).
*
* @see ActiveSpan
* @see Finishable
* @see Tracer.SpanBuilder#startManual()
* @see Tracer.SpanBuilder#startActive()
*/
public interface Span extends BaseSpan<Span> {
/**
* Sets the end timestamp to now and records the span.
*
* <p>With the exception of calls to {@link #context}, this should be the last call made to the span instance.
* Future calls to {@link #finish} are defined as noops, and future calls to methods other than {@link #context}
* lead to undefined behavior.
*
* @see Span#context()
*/
void finish();

/**
* Sets an explicit end timestamp and records the span.
*
* <p>With the exception of calls to Span.context(), this should be the last call made to the span instance, and to
* do otherwise leads to undefined behavior.
*
* @param finishMicros an explicit finish time, in microseconds since the epoch
*
* @see Span#context()
*/
void finish(long finishMicros);
public interface Span extends BaseSpan<Span>, Finishable {
}
9 changes: 9 additions & 0 deletions opentracing-api/src/main/java/io/opentracing/Tracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ interface SpanBuilder {
*/
ActiveSpan startActive();

/**
* Like {@link #startActive()}, but with the given {@link ActiveSpan.Observer} keeping track of the returned
* ActiveSpan.
*
* @param observer
* @return
*/
ActiveSpan startActive(ActiveSpan.Observer observer);

/**
* Like {@link #startActive()}, but the returned {@link Span} has not been registered via the
* {@link ActiveSpanSource}.
Expand Down
11 changes: 11 additions & 0 deletions opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public ActiveSpan makeActive(Span span) {
return spanSource.makeActive(span);
}

@Override
public ActiveSpan makeActive(Span span, ActiveSpan.Observer observer) {
return spanSource.makeActive(span, observer);
}

/**
* Propagator allows the developer to intercept and verify any calls to inject() and/or extract().
*
Expand Down Expand Up @@ -272,6 +277,12 @@ public ActiveSpan startActive() {
return spanSource.makeActive(span);
}

@Override
public ActiveSpan startActive(ActiveSpan.Observer observer) {
MockSpan span = this.startManual();
return spanSource.makeActive(span, observer);
}

@Override
public MockSpan startManual() {
if (this.startMicros == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public ActiveSpan makeActive(Span span) {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}

@Override
public ActiveSpan makeActive(Span span, ActiveSpan.Observer observer) {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}

@Override
public ActiveSpan activeSpan() { return null; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ public ActiveSpan startActive() {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}

@Override
public ActiveSpan startActive(ActiveSpan.Observer observer) {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}

@Override
public Span startManual() {
return NoopSpanImpl.INSTANCE;
Expand All @@ -88,4 +93,4 @@ public Iterable<Map.Entry<String, String>> baggageItems() {

@Override
public String toString() { return NoopSpanBuilder.class.getSimpleName(); }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,10 @@ public ActiveSpan activeSpan() {
public ActiveSpan makeActive(Span span) {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}

@Override
public ActiveSpan makeActive(Span span, ActiveSpan.Observer observer) {
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.opentracing.util;

import io.opentracing.ActiveSpan;
import io.opentracing.ActiveSpanSource;
import io.opentracing.Span;
import io.opentracing.Finishable;

import java.util.concurrent.atomic.AtomicInteger;

/**
* {@link AutoFinisher} is an {@link ActiveSpan.Observer} that automatically {@link Span#finish()}es the
* underlying {@link Span} when there are zero remaining {@link ActiveSpan}s or {@link ActiveSpan.Continuation}s
* referencing that underlying {@link Span}.
*
* <p>
* Use {@link AutoFinisher} like this:
* <pre><code>
* try (ActiveSpan span = tracer.buildSpan("...").startActive(new AutoFinisher())) {
* // (Do work, even if deferred via {@link ActiveSpan#capture()})
* span.setTag( ... ); // etc, etc
* } // Span finish()es automatically when there are no longer any ActiveSpans or Continuations referring to it
* </code></pre>
*
* <p>
* Note that {@link AutoFinisher} works by counting the number of extant {@link ActiveSpan} and
* {@link ActiveSpan.Continuation} references to the underlying {@link Span} provided at
* {@link ActiveSpanSource#makeActive(Span, ActiveSpan.Observer)}
* </p>
*/
public class AutoFinisher implements ActiveSpan.Observer {
private final AtomicInteger refCount;

public AutoFinisher() {
refCount = new AtomicInteger(1);
}

@Override
public void afterCapture(ActiveSpan captured, ActiveSpan.Continuation destination) {
// Always increment the reference count when new Continuations are created (i.e., we assume that all
// Continuations are eventually activate()d).
refCount.incrementAndGet();
}

@Override
public void afterActivate(ActiveSpan.Continuation source, ActiveSpan justActivated) {}

@Override
public void afterDeactivate(ActiveSpan activeSpan, Finishable finisher) {
if (0 == refCount.decrementAndGet()) {
finisher.finish();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,9 @@ public ActiveSpan activeSpan() {
public ActiveSpan makeActive(Span span) {
return tracer.makeActive(span);
}

@Override
public ActiveSpan makeActive(Span span, ActiveSpan.Observer observer) {
return tracer.makeActive(span, observer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package io.opentracing.util;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import io.opentracing.ActiveSpan;
import io.opentracing.ActiveSpanSource;
Expand All @@ -33,13 +32,13 @@ public class ThreadLocalActiveSpan implements ActiveSpan {
private final ThreadLocalActiveSpanSource source;
private final Span wrapped;
private final ThreadLocalActiveSpan toRestore;
private final AtomicInteger refCount;
private final Observer observer;

ThreadLocalActiveSpan(ThreadLocalActiveSpanSource source, Span wrapped, AtomicInteger refCount) {
ThreadLocalActiveSpan(ThreadLocalActiveSpanSource source, Span wrapped, Observer observer) {
this.source = source;
this.refCount = refCount;
this.wrapped = wrapped;
this.toRestore = source.tlsSnapshot.get();
this.observer = observer;
source.tlsSnapshot.set(this);
}

Expand All @@ -50,15 +49,18 @@ public void deactivate() {
return;
}
source.tlsSnapshot.set(toRestore);

if (0 == refCount.decrementAndGet()) {
wrapped.finish();
if (observer != null) {
observer.afterDeactivate(this, this.wrapped);
}
}

@Override
public Continuation capture() {
return new ThreadLocalActiveSpan.Continuation();
Continuation cont = new ThreadLocalActiveSpan.Continuation();
if (observer != null) {
observer.afterCapture(ThreadLocalActiveSpan.this, cont);
}
return cont;
}

@Override
Expand Down Expand Up @@ -136,13 +138,13 @@ public String toString() {
}

private final class Continuation implements ActiveSpan.Continuation {
Continuation() {
refCount.incrementAndGet();
}

@Override
public ThreadLocalActiveSpan activate() {
return new ThreadLocalActiveSpan(source, wrapped, refCount);
ThreadLocalActiveSpan rval = new ThreadLocalActiveSpan(source, wrapped, observer);
if (observer != null) {
observer.afterActivate(this, rval);
}
return rval;
}
}

Expand Down
Loading