diff --git a/opentracing-api/src/main/java/io/opentracing/ActiveSpan.java b/opentracing-api/src/main/java/io/opentracing/ActiveSpan.java deleted file mode 100644 index 46e09d08..00000000 --- a/opentracing-api/src/main/java/io/opentracing/ActiveSpan.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2016-2017 The OpenTracing Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.opentracing; - -import java.io.Closeable; - -/** - * {@link ActiveSpan} inherits all of the OpenTracing functionality in {@link BaseSpan} and layers on in-process - * propagation capabilities. - * - *

- * In any given thread there is at most one {@link ActiveSpan "active" span} primarily responsible for the work - * accomplished by the surrounding application code. That {@link ActiveSpan} may be accessed via the - * {@link ActiveSpanSource#activeSpan()} method. If the application needs to defer work that should be part of - * the same Span, the Source provides a {@link ActiveSpan#capture} method that returns a {@link Continuation}; - * this continuation may be used to re-activate and continue the {@link Span} in that other asynchronous executor - * and/or thread. - * - *

- * {@link ActiveSpan}s are created via {@link Tracer.SpanBuilder#startActive()} or, less commonly, - * {@link ActiveSpanSource#makeActive}. Per the above, they can be {@link ActiveSpan#capture()}ed as - * {@link ActiveSpan.Continuation}s, then re-{@link Continuation#activate()}d later. - * - *

- * NOTE: {@link ActiveSpan} extends {@link Closeable} rather than {@code AutoCloseable} in order to preserve support - * for JDK1.6. - * - * @see Tracer.SpanBuilder#startActive() - * @see Continuation#activate() - * @see ActiveSpanSource - * @see BaseSpan - * @see Span - */ -public interface ActiveSpan extends Closeable, BaseSpan { - /** - * Mark the end of the active period for the current thread and {@link ActiveSpan}. When the last - * {@link ActiveSpan} is deactivated for a given {@link Span}, it is automatically {@link Span#finish()}ed. - * - *

- * NOTE: Calling {@link #deactivate} more than once on a single {@link ActiveSpan} instance leads to undefined - * behavior. - * - * @see Closeable#close() {@link ActiveSpan}s are auto-closeable and may be used in try-with-resources blocks - */ - void deactivate(); - - /** - * A synonym for {@link #deactivate()} that can be used in try-with-resources blocks. - */ - @Override - void close(); - - /** - * "Capture" a new {@link Continuation} associated with this {@link ActiveSpan} and {@link Span}, as well as any - * 3rd-party execution context of interest. The {@link Continuation} may be used as data in a closure or callback - * function where the {@link ActiveSpan} may be resumed and reactivated. - * - *

- * IMPORTANT: the caller MUST {@link Continuation#activate()} and {@link ActiveSpan#deactivate()} the - * returned {@link Continuation} or the associated {@link Span} will never automatically {@link Span#finish()}. - * That is, calling {@link #capture()} increments a refcount that must be decremented somewhere else. - * - *

- * The associated {@link Span} will not {@link Span#finish()} while a {@link Continuation} is outstanding; in - * this way, it provides a reference/pin just like an {@link ActiveSpan} does. - * - * @return a new {@link Continuation} to {@link Continuation#activate()} at the appropriate time. - */ - Continuation capture(); - - /** - * A {@link Continuation} can be used once to activate a Span along with any non-OpenTracing execution - * context (e.g., MDC), then deactivate when processing activity moves on to another Span. (In practice, this - * active period typically extends for the length of a deferred async closure invocation.) - * - *

- * Most users do not directly interact with {@link Continuation}, {@link Continuation#activate()} or - * {@link ActiveSpan#deactivate()}, but rather use {@link ActiveSpanSource}-aware Runnables/Callables/Executors. - * Those higher-level primitives do not need to be defined within the OpenTracing core API, and so - * they are not. - * - * @see ActiveSpanSource#makeActive(Span) - */ - interface Continuation { - /** - * Make the Span (and other execution context) encapsulated by this {@link Continuation} active and - * return it. - * - *

- * NOTE: It is an error to call activate() more than once on a single Continuation instance. - * - * @see ActiveSpanSource#makeActive(Span) - * @return a handle to the newly-activated {@link ActiveSpan} - */ - ActiveSpan activate(); - } - -} diff --git a/opentracing-api/src/main/java/io/opentracing/ActiveSpanSource.java b/opentracing-api/src/main/java/io/opentracing/ActiveSpanSource.java deleted file mode 100644 index 334d00e8..00000000 --- a/opentracing-api/src/main/java/io/opentracing/ActiveSpanSource.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2016-2017 The OpenTracing Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.opentracing; - -/** - * {@link ActiveSpanSource} allows an existing (possibly thread-local-aware) execution context provider to act as a - * source for an actively-scheduled OpenTracing Span. - * - *

- * {@link ActiveSpanSource} is a super-interface to {@link Tracer}, so note that all {@link Tracer}s fulfill the - * {@link ActiveSpanSource} contract. - * - * @see ActiveSpan - */ -public interface ActiveSpanSource { - - /** - * Return the {@link ActiveSpan active span}. This does not affect the internal reference count for the - * {@link ActiveSpan}. - * - *

- * If there is an {@link ActiveSpan active span}, it becomes an implicit parent of any newly-created - * {@link BaseSpan span} at {@link Tracer.SpanBuilder#startActive()} time (rather than at - * {@link Tracer#buildSpan(String)} time). - * - * @return the {@link ActiveSpan active span}, or null if none could be found. - */ - ActiveSpan activeSpan(); - - /** - * 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} - * @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); -} diff --git a/opentracing-api/src/main/java/io/opentracing/BaseSpan.java b/opentracing-api/src/main/java/io/opentracing/BaseSpan.java deleted file mode 100644 index b83bc190..00000000 --- a/opentracing-api/src/main/java/io/opentracing/BaseSpan.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2016-2017 The OpenTracing Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.opentracing; - -import java.util.Map; - -/** - * {@link BaseSpan} represents the OpenTracing specification's span contract with the exception of methods to finish - * said span. For those, either use {@link Span#finish()} or {@link ActiveSpan#deactivate()} depending on the - * programming model. - * - * @see Span - * @see ActiveSpan - * @see Tracer.SpanBuilder#startManual() - * @see Tracer.SpanBuilder#startActive() - */ -public interface BaseSpan { - /** - * Retrieve the associated SpanContext. - * - * This may be called at any time, including after calls to finish(). - * - * @return the SpanContext that encapsulates Span state that should propagate across process boundaries. - */ - SpanContext context(); - - /** - * Set a key:value tag on the Span. - */ - S setTag(String key, String value); - - /** Same as {@link #setTag(String, String)}, but for boolean values. */ - S setTag(String key, boolean value); - - /** Same as {@link #setTag(String, String)}, but for numeric values. */ - S setTag(String key, Number value); - - /** - * Log key:value pairs to the Span with the current walltime timestamp. - * - *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. - * Caveat emptor. - * - *

A contrived example (using Guava, which is not required): - *


-     span.log(
-     ImmutableMap.Builder()
-     .put("event", "soft error")
-     .put("type", "cache timeout")
-     .put("waited.millis", 1500)
-     .build());
-     
- * - * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; - * some may also support arbitrary Objects. - * @return the Span, for chaining - * @see Span#log(String) - */ - S log(Map fields); - - /** - * Like log(Map<String, Object>), but with an explicit timestamp. - * - *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. - * Caveat emptor. - * - * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the - * Span's start timestamp. - * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; - * some may also support arbitrary Objects. - * @return the Span, for chaining - * @see Span#log(long, String) - */ - S log(long timestampMicroseconds, Map fields); - - /** - * Record an event at the current walltime timestamp. - * - * Shorthand for - * - *


-     span.log(Collections.singletonMap("event", event));
-     
- * - * @param event the event value; often a stable identifier for a moment in the Span lifecycle - * @return the Span, for chaining - */ - S log(String event); - - /** - * Record an event at a specific timestamp. - * - * Shorthand for - * - *

-     span.log(timestampMicroseconds, Collections.singletonMap("event", event));
-     
- * - * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the - * Span's start timestamp. - * @param event the event value; often a stable identifier for a moment in the Span lifecycle - * @return the Span, for chaining - */ - S log(long timestampMicroseconds, String event); - - /** - * Sets a baggage item in the Span (and its SpanContext) as a key/value pair. - * - * Baggage enables powerful distributed context propagation functionality where arbitrary application data can be - * carried along the full path of request execution throughout the system. - * - * Note 1: Baggage is only propagated to the future (recursive) children of this SpanContext. - * - * Note 2: Baggage is sent in-band with every subsequent local and remote calls, so this feature must be used with - * care. - * - * @return this Span instance, for chaining - */ - S setBaggageItem(String key, String value); - - /** - * @return the value of the baggage item identified by the given key, or null if no such item could be found - */ - String getBaggageItem(String key); - - /** - * Sets the string name for the logical operation this span represents. - * - * @return this Span instance, for chaining - */ - S setOperationName(String operationName); -} diff --git a/opentracing-api/src/main/java/io/opentracing/Scope.java b/opentracing-api/src/main/java/io/opentracing/Scope.java new file mode 100644 index 00000000..dd997647 --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/Scope.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing; + +import java.io.Closeable; + +/** + * A {@link Scope} formalizes the activation and deactivation of a {@link Span}, usually from a CPU standpoint. + * + *

+ * Many times a {@link Span} will be extant (in that {@link Span#finish()} has not been called) despite being in a + * non-runnable state from a CPU/scheduler standpoint. For instance, a {@link Span} representing the client side of an + * RPC will be unfinished but blocked on IO while the RPC is still outstanding. A {@link Scope} defines when a given + * {@link Span} is scheduled and on the path. + */ +public interface Scope extends Closeable { + /** + * Mark the end of the active period for the current thread and {@link Scope}, + * updating the {@link ScopeManager#active()} in the process. + * + *

+ * NOTE: Calling {@link #close} more than once on a single {@link Scope} instance leads to undefined + * behavior. + */ + @Override + void close(); + + /** + * @return the {@link Span} that's been scoped by this {@link Scope} + */ + Span span(); +} diff --git a/opentracing-api/src/main/java/io/opentracing/ScopeManager.java b/opentracing-api/src/main/java/io/opentracing/ScopeManager.java new file mode 100644 index 00000000..b37f634f --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/ScopeManager.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing; + +/** + * The {@link ScopeManager} interface abstracts both the activation of {@link Span} instances (via + * {@link ScopeManager#activate(Span)}) and access to an active {@link Span}/{@link Scope} + * (via {@link ScopeManager#active()}). + * + * @see Scope + * @see Tracer#scopeManager() + */ +public interface ScopeManager { + /** + * Make a {@link Span} instance active. + * + * @param span the {@link Span} that should become the {@link #active()} + * @return a {@link Scope} instance to control the end of the active period for the {@link Span}. It is a + * programming error to neglect to call {@link Scope#close()} on the returned instance. + */ + Scope activate(Span span); + Scope activate(Span span, boolean finishOnClose); + + /** + * Return the currently active {@link Scope} which can be used to access the currently active + * {@link Scope#span()}. + * + *

+ * If there is an {@link Scope non-null scope}, its wrapped {@link Span} becomes an implicit parent of any + * newly-created {@link Span} at {@link Tracer.SpanBuilder#startActive()} time (rather than at + * {@link Tracer#buildSpan(String)} time). + * + * @return the {@link Scope active scope}, or null if none could be found. + */ + Scope active(); +} diff --git a/opentracing-api/src/main/java/io/opentracing/Span.java b/opentracing-api/src/main/java/io/opentracing/Span.java index 026c4ce5..548ac4f8 100644 --- a/opentracing-api/src/main/java/io/opentracing/Span.java +++ b/opentracing-api/src/main/java/io/opentracing/Span.java @@ -13,16 +13,132 @@ */ package io.opentracing; +import java.util.Map; + /** - * Represents an in-flight Span that's manually propagated within the given process. Most of - * the API lives in {@link BaseSpan}. - * - *

{@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. + * {@link Span} represents the OpenTracing specification's Span contract. * - * @see ActiveSpan for automatic propagation (recommended for most intstrumentation!) + * @see Scope + * @see ScopeManager + * @see Tracer.SpanBuilder#startManual() + * @see Tracer.SpanBuilder#startActive() */ -public interface Span extends BaseSpan { +public interface Span { + /** + * Retrieve the associated SpanContext. + * + * This may be called at any time, including after calls to finish(). + * + * @return the SpanContext that encapsulates Span state that should propagate across process boundaries. + */ + SpanContext context(); + + /** + * Set a key:value tag on the Span. + */ + Span setTag(String key, String value); + + /** Same as {@link #setTag(String, String)}, but for boolean values. */ + Span setTag(String key, boolean value); + + /** Same as {@link #setTag(String, String)}, but for numeric values. */ + Span setTag(String key, Number value); + + /** + * Log key:value pairs to the Span with the current walltime timestamp. + * + *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. + * Caveat emptor. + * + *

A contrived example (using Guava, which is not required): + *


+     span.log(
+     ImmutableMap.Builder()
+     .put("event", "soft error")
+     .put("type", "cache timeout")
+     .put("waited.millis", 1500)
+     .build());
+     
+ * + * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; + * some may also support arbitrary Objects. + * @return the Span, for chaining + * @see Span#log(String) + */ + Span log(Map fields); + + /** + * Like log(Map<String, Object>), but with an explicit timestamp. + * + *

CAUTIONARY NOTE: not all Tracer implementations support key:value log fields end-to-end. + * Caveat emptor. + * + * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the + * Span's start timestamp. + * @param fields key:value log fields. Tracer implementations should support String, numeric, and boolean values; + * some may also support arbitrary Objects. + * @return the Span, for chaining + * @see Span#log(long, String) + */ + Span log(long timestampMicroseconds, Map fields); + + /** + * Record an event at the current walltime timestamp. + * + * Shorthand for + * + *


+     span.log(Collections.singletonMap("event", event));
+     
+ * + * @param event the event value; often a stable identifier for a moment in the Span lifecycle + * @return the Span, for chaining + */ + Span log(String event); + + /** + * Record an event at a specific timestamp. + * + * Shorthand for + * + *

+     span.log(timestampMicroseconds, Collections.singletonMap("event", event));
+     
+ * + * @param timestampMicroseconds The explicit timestamp for the log record. Must be greater than or equal to the + * Span's start timestamp. + * @param event the event value; often a stable identifier for a moment in the Span lifecycle + * @return the Span, for chaining + */ + Span log(long timestampMicroseconds, String event); + + /** + * Sets a baggage item in the Span (and its SpanContext) as a key/value pair. + * + * Baggage enables powerful distributed context propagation functionality where arbitrary application data can be + * carried along the full path of request execution throughout the system. + * + * Note 1: Baggage is only propagated to the future (recursive) children of this SpanContext. + * + * Note 2: Baggage is sent in-band with every subsequent local and remote calls, so this feature must be used with + * care. + * + * @return this Span instance, for chaining + */ + Span setBaggageItem(String key, String value); + + /** + * @return the value of the baggage item identified by the given key, or null if no such item could be found + */ + String getBaggageItem(String key); + + /** + * Sets the string name for the logical operation this span represents. + * + * @return this Span instance, for chaining + */ + Span setOperationName(String operationName); + /** * Sets the end timestamp to now and records the span. * diff --git a/opentracing-api/src/main/java/io/opentracing/Tracer.java b/opentracing-api/src/main/java/io/opentracing/Tracer.java index 25d3d321..ab6da828 100644 --- a/opentracing-api/src/main/java/io/opentracing/Tracer.java +++ b/opentracing-api/src/main/java/io/opentracing/Tracer.java @@ -18,7 +18,12 @@ /** * Tracer is a simple, thin interface for Span creation and propagation across arbitrary transports. */ -public interface Tracer extends ActiveSpanSource { +public interface Tracer { + + /** + * @return the current {@link ScopeManager}, which may be a noop but may not be null. + */ + ScopeManager scopeManager(); /** * Return a new SpanBuilder for a Span with the given `operationName`. @@ -29,14 +34,14 @@ public interface Tracer extends ActiveSpanSource { *

      *   Tracer tracer = ...
      *
-     *   // Note: if there is a `tracer.activeSpan()`, it will be used as the target of an implicit CHILD_OF
-     *   // Reference for "workSpan" when `startActive()` is invoked.
-     *   try (ActiveSpan workSpan = tracer.buildSpan("DoWork").startActive()) {
-     *       workSpan.setTag("...", "...");
+     *   // Note: if there is a `tracer.active()` Scope, its `span()` will be used as the target
+     *   // of an implicit CHILD_OF Reference for "workScope.span()" when `startActive()` is invoked.
+     *   try (Scope workScope = tracer.buildSpan("DoWork").startActive()) {
+     *       workScope.span().setTag("...", "...");
      *       // etc, etc
      *   }
      *
-     *   // It's also possible to create Spans manually, bypassing the ActiveSpanSource activation.
+     *   // It's also possible to create Spans manually, bypassing the ScopeManager activation.
      *   Span http = tracer.buildSpan("HandleHTTPRequest")
      *                     .asChildOf(rpcSpanContext)  // an explicit parent
      *                     .withTag("user_agent", req.UserAgent)
@@ -111,7 +116,7 @@ interface SpanBuilder {
          * 

* If parent==null, this is a noop. */ - SpanBuilder asChildOf(BaseSpan parent); + SpanBuilder asChildOf(Span parent); /** * Add a reference from the Span being built to a distinct (usually parent) Span. May be called multiple times @@ -120,12 +125,12 @@ interface SpanBuilder { *

* If *

    - *
  • the {@link Tracer}'s {@link ActiveSpanSource#activeSpan()} is not null, and + *
  • the {@link Tracer}'s {@link ScopeManager#active()} is not null, and *
  • no explicit references are added via {@link SpanBuilder#addReference}, and *
  • {@link SpanBuilder#ignoreActiveSpan()} is not invoked, *
* ... then an inferred {@link References#CHILD_OF} reference is created to the - * {@link ActiveSpanSource#activeSpan()} {@link SpanContext} when either {@link SpanBuilder#startActive()} or + * {@link ScopeManager#active()} {@link SpanContext} when either {@link SpanBuilder#startActive()} or * {@link SpanBuilder#startManual} is invoked. * * @param referenceType the reference type, typically one of the constants defined in References @@ -138,7 +143,7 @@ interface SpanBuilder { SpanBuilder addReference(String referenceType, SpanContext referencedContext); /** - * Do not create an implicit {@link References#CHILD_OF} reference to the {@link ActiveSpanSource#activeSpan}). + * Do not create an implicit {@link References#CHILD_OF} reference to the {@link ScopeManager#active()}). */ SpanBuilder ignoreActiveSpan(); @@ -155,46 +160,47 @@ interface SpanBuilder { SpanBuilder withStartTimestamp(long microseconds); /** - * Returns a newly started and activated {@link ActiveSpan}. + * Returns a newly started and activated {@link Scope}. * *

- * The returned {@link ActiveSpan} supports try-with-resources. For example: + * The returned {@link Scope} supports try-with-resources. For example: *


-         *     try (ActiveSpan span = tracer.buildSpan("...").startActive()) {
+         *     try (Scope scope = tracer.buildSpan("...").startActive()) {
          *         // (Do work)
-         *         span.setTag( ... );  // etc, etc
-         *     }  // Span finishes automatically unless deferred via {@link ActiveSpan#capture}
+         *         scope.span().setTag( ... );  // etc, etc
+         *     }  // XXX Span finishes automatically
          * 
* *

* If *

    - *
  • the {@link Tracer}'s {@link ActiveSpanSource#activeSpan()} is not null, and + *
  • the {@link Tracer}'s {@link ScopeManager#active()} is not null, and *
  • no explicit references are added via {@link SpanBuilder#addReference}, and *
  • {@link SpanBuilder#ignoreActiveSpan()} is not invoked, *
* ... then an inferred {@link References#CHILD_OF} reference is created to the - * {@link ActiveSpanSource#activeSpan()}'s {@link SpanContext} when either + * {@link ScopeManager#active()}'s {@link SpanContext} when either * {@link SpanBuilder#startManual()} or {@link SpanBuilder#startActive} is invoked. * *

* Note: {@link SpanBuilder#startActive()} is a shorthand for - * {@code tracer.makeActive(spanBuilder.startManual())}. + * {@code tracer.scopeManager().activate(spanBuilder.startManual())}. * - * @return an {@link ActiveSpan}, already registered via the {@link ActiveSpanSource} + * @return an {@link Scope}, already registered via the {@link ScopeManager} * - * @see ActiveSpanSource - * @see ActiveSpan + * @see ScopeManager + * @see Scope */ - ActiveSpan startActive(); + Scope startActive(); + Scope startActive(boolean finishOnClose); /** * Like {@link #startActive()}, but the returned {@link Span} has not been registered via the - * {@link ActiveSpanSource}. + * {@link ScopeManager}. * * @see SpanBuilder#startActive() * @return the newly-started Span instance, which has *not* been automatically registered - * via the {@link ActiveSpanSource} + * via the {@link ScopeManager} */ Span startManual(); diff --git a/opentracing-api/src/main/java/io/opentracing/tag/AbstractTag.java b/opentracing-api/src/main/java/io/opentracing/tag/AbstractTag.java index 4fed5c18..792ca636 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/AbstractTag.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/AbstractTag.java @@ -13,7 +13,7 @@ */ package io.opentracing.tag; -import io.opentracing.BaseSpan; +import io.opentracing.Span; public abstract class AbstractTag { protected final String key; @@ -26,5 +26,5 @@ public String getKey() { return key; } - protected abstract void set(BaseSpan span, T tagValue); + protected abstract void set(Span span, T tagValue); } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/BooleanTag.java b/opentracing-api/src/main/java/io/opentracing/tag/BooleanTag.java index f9be1ee3..8168d422 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/BooleanTag.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/BooleanTag.java @@ -13,7 +13,7 @@ */ package io.opentracing.tag; -import io.opentracing.BaseSpan; +import io.opentracing.Span; public class BooleanTag extends AbstractTag { public BooleanTag(String key) { @@ -21,7 +21,7 @@ public BooleanTag(String key) { } @Override - public void set(BaseSpan span, Boolean tagValue) { + public void set(Span span, Boolean tagValue) { span.setTag(super.key, tagValue); } } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/IntOrStringTag.java b/opentracing-api/src/main/java/io/opentracing/tag/IntOrStringTag.java index e173b842..1ea09cbf 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/IntOrStringTag.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/IntOrStringTag.java @@ -13,14 +13,14 @@ */ package io.opentracing.tag; -import io.opentracing.BaseSpan; +import io.opentracing.Span; public class IntOrStringTag extends IntTag { public IntOrStringTag(String key) { super(key); } - public void set(BaseSpan span, String tagValue) { + public void set(Span span, String tagValue) { span.setTag(super.key, tagValue); } } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/IntTag.java b/opentracing-api/src/main/java/io/opentracing/tag/IntTag.java index 48f4ae71..cb126492 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/IntTag.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/IntTag.java @@ -13,7 +13,7 @@ */ package io.opentracing.tag; -import io.opentracing.BaseSpan; +import io.opentracing.Span; public class IntTag extends AbstractTag { public IntTag(String key) { @@ -21,7 +21,7 @@ public IntTag(String key) { } @Override - public void set(BaseSpan span, Integer tagValue) { + public void set(Span span, Integer tagValue) { span.setTag(super.key, tagValue); } } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/StringTag.java b/opentracing-api/src/main/java/io/opentracing/tag/StringTag.java index 1fdba21a..65cfd59e 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/StringTag.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/StringTag.java @@ -13,7 +13,7 @@ */ package io.opentracing.tag; -import io.opentracing.BaseSpan; +import io.opentracing.Span; public class StringTag extends AbstractTag { public StringTag(String key) { @@ -21,11 +21,11 @@ public StringTag(String key) { } @Override - public void set(BaseSpan span, String tagValue) { + public void set(Span span, String tagValue) { span.setTag(super.key, tagValue); } - public void set(BaseSpan span, StringTag tag) { + public void set(Span span, StringTag tag) { span.setTag(super.key, tag.key); } } diff --git a/opentracing-api/src/test/java/io/opentracing/tag/AbstractTagTest.java b/opentracing-api/src/test/java/io/opentracing/tag/AbstractTagTest.java index 816e259f..f327851e 100644 --- a/opentracing-api/src/test/java/io/opentracing/tag/AbstractTagTest.java +++ b/opentracing-api/src/test/java/io/opentracing/tag/AbstractTagTest.java @@ -17,10 +17,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import org.junit.Test; - -import io.opentracing.ActiveSpan; import io.opentracing.Span; +import org.junit.Test; /** * @author Pavol Loffay @@ -45,16 +43,4 @@ public void testSetTagOnSpan() { verify(activeSpan).setTag(key, value); } - - @Test - public void testSetTagOnActiveSpan() { - String value = "foo"; - String key = "bar"; - - ActiveSpan activeSpan = mock(ActiveSpan.class); - StringTag tag = new StringTag(key); - tag.set(activeSpan, value); - - verify(activeSpan).setTag(key, value); - } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScope.java b/opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScope.java new file mode 100644 index 00000000..0010a54e --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScope.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples; + +import io.opentracing.Scope; +import io.opentracing.Span; + +import java.util.concurrent.atomic.AtomicInteger; + +public class AutoFinishScope implements Scope { + final AutoFinishScopeManager manager; + final AtomicInteger refCount; + private final Span wrapped; + private final AutoFinishScope toRestore; + + AutoFinishScope(AutoFinishScopeManager manager, AtomicInteger refCount, Span wrapped) { + this.manager = manager; + this.refCount = refCount; + this.wrapped = wrapped; + this.toRestore = manager.tlsScope.get(); + manager.tlsScope.set(this); + } + + public class Continuation { + public Continuation() { + refCount.incrementAndGet(); + } + + public AutoFinishScope activate() { + return new AutoFinishScope(manager, refCount, wrapped); + } + } + + public Continuation capture() { + return new Continuation(); + } + + @Override + public void close() { + if (manager.tlsScope.get() != this) { + return; + } + + if (refCount.decrementAndGet() == 0) { + wrapped.finish(); + } + + manager.tlsScope.set(toRestore); + } + + @Override + public Span span() { + return wrapped; + } +} diff --git a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpanSource.java b/opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScopeManager.java similarity index 51% rename from opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpanSource.java rename to opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScopeManager.java index 2cd7e6e2..bfb578bd 100644 --- a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpanSource.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/AutoFinishScopeManager.java @@ -11,32 +11,29 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package io.opentracing.util; +package io.opentracing.examples; -import io.opentracing.ActiveSpan; -import io.opentracing.ActiveSpanSource; +import io.opentracing.ScopeManager; import io.opentracing.Span; -import io.opentracing.Tracer; import java.util.concurrent.atomic.AtomicInteger; -/** - * A simple {@link ActiveSpanSource} implementation built on top of Java's thread-local storage primitive. - * - * @see ThreadLocalActiveSpan - * @see Tracer#activeSpan() - */ -public class ThreadLocalActiveSpanSource implements ActiveSpanSource { - final ThreadLocal tlsSnapshot = new ThreadLocal(); +public class AutoFinishScopeManager implements ScopeManager { + final ThreadLocal tlsScope = new ThreadLocal(); + + @Override + public AutoFinishScope activate(Span span) { + return new AutoFinishScope(this, new AtomicInteger(1), span); + } @Override - public ThreadLocalActiveSpan activeSpan() { - return tlsSnapshot.get(); + public AutoFinishScope activate(Span span, boolean finishOnClose) { + return new AutoFinishScope(this, new AtomicInteger(1), span); } @Override - public ActiveSpan makeActive(Span span) { - return new ThreadLocalActiveSpan(this, span, new AtomicInteger(1)); + public AutoFinishScope active() { + return tlsScope.get(); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/RunnableAction.java b/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/RunnableAction.java index fab14632..bda704d3 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/RunnableAction.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/RunnableAction.java @@ -13,8 +13,8 @@ */ package io.opentracing.examples.activate_deactivate; -import io.opentracing.ActiveSpan; -import io.opentracing.ActiveSpan.Continuation; +import io.opentracing.examples.AutoFinishScope; +import io.opentracing.examples.AutoFinishScope.Continuation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,8 +30,8 @@ public class RunnableAction implements Runnable { private final Continuation continuation; - RunnableAction(ActiveSpan activeSpan) { - continuation = activeSpan.capture(); + RunnableAction(AutoFinishScope scope) { + continuation = scope.capture(); logger.info("Action created"); } @@ -42,7 +42,7 @@ public class RunnableAction implements Runnable { @Override public void run() { logger.info("Action started"); - ActiveSpan activeSpan = continuation.activate(); + AutoFinishScope scope = continuation.activate(); try { TimeUnit.SECONDS.sleep(1); // without sleep first action can finish before second is started @@ -51,9 +51,9 @@ public void run() { } // set random tag starting with 'test_tag_' to test that finished span has all of them - activeSpan.setTag("test_tag_" + ThreadLocalRandom.current().nextInt(), "random"); + scope.span().setTag("test_tag_" + ThreadLocalRandom.current().nextInt(), "random"); - activeSpan.deactivate(); + scope.close(); logger.info("Action finished"); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/ScheduledActionsTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/ScheduledActionsTest.java index c42ad772..cc5307c2 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/ScheduledActionsTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/activate_deactivate/ScheduledActionsTest.java @@ -13,11 +13,12 @@ */ package io.opentracing.examples.activate_deactivate; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; +import io.opentracing.examples.AutoFinishScope; +import io.opentracing.examples.AutoFinishScopeManager; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; -import io.opentracing.util.ThreadLocalActiveSpanSource; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +38,7 @@ public class ScheduledActionsTest { private static final Logger logger = LoggerFactory.getLogger(ScheduledActionsTest.class); - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new AutoFinishScopeManager(), Propagator.TEXT_MAP); private final ScheduledExecutorService service = Executors.newScheduledThreadPool(10); @@ -88,8 +89,8 @@ private Thread entryThread() { @Override public void run() { logger.info("Entry thread started"); - try (ActiveSpan activeSpan = tracer.buildSpan("parent").startActive()) { - Runnable action = new RunnableAction(activeSpan); + try (Scope scope = tracer.buildSpan("parent").startActive()) { + Runnable action = new RunnableAction((AutoFinishScope)scope); // Action is executed at some time and we are not able to check status service.schedule(action, 500, TimeUnit.MILLISECONDS); @@ -107,9 +108,9 @@ private Thread entryThreadWithTwoActions() { @Override public void run() { logger.info("Entry thread 2x started"); - try (ActiveSpan activeSpan = tracer.buildSpan("parent").startActive()) { - Runnable action = new RunnableAction(activeSpan); - Runnable action2 = new RunnableAction(activeSpan); + try (Scope scope = tracer.buildSpan("parent").startActive()) { + Runnable action = new RunnableAction((AutoFinishScope)scope); + Runnable action2 = new RunnableAction((AutoFinishScope)scope); Random random = new Random(); diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/active_span_replacement/ActiveSpanReplacementTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/active_span_replacement/ActiveSpanReplacementTest.java index 7c907014..72cb979b 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/active_span_replacement/ActiveSpanReplacementTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/active_span_replacement/ActiveSpanReplacementTest.java @@ -13,11 +13,12 @@ */ package io.opentracing.examples.active_span_replacement; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; +import io.opentracing.Span; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.Test; import java.util.List; @@ -35,7 +36,7 @@ public class ActiveSpanReplacementTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); private final ExecutorService executor = Executors.newCachedThreadPool(); @@ -43,8 +44,9 @@ public class ActiveSpanReplacementTest { @Test public void test() throws Exception { // Start an isolated task and query for its result in another task/thread - try (ActiveSpan span = tracer.buildSpan("initial").startActive()) { - submitAnotherTask(span); + try (Scope scope = tracer.buildSpan("initial").startActive(false)) { + // Explicitly pass a Span to be finished once a late calculation is done. + submitAnotherTask(scope.span()); } await().atMost(15, TimeUnit.SECONDS).until(finishedSpansSize(tracer), equalTo(3)); @@ -63,25 +65,25 @@ public void test() throws Exception { assertNotEquals(spans.get(0).context().traceId(), spans.get(1).context().traceId()); assertEquals(0, spans.get(0).parentId()); - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } - private void submitAnotherTask(ActiveSpan span) { - final ActiveSpan.Continuation cont = span.capture(); + private void submitAnotherTask(final Span initialSpan) { executor.submit(new Runnable() { @Override public void run() { // Create a new Span for this task - try (ActiveSpan taskSpan = tracer.buildSpan("task").startActive()) { + try (Scope taskScope = tracer.buildSpan("task").startActive()) { // Simulate work strictly related to the initial Span - try (ActiveSpan initialSpan = cont.activate()) { + // and finish it. + try (Scope initialScope = tracer.scopeManager().activate(initialSpan)) { sleep(50); } // Restore the span for this task and create a subspan - try (ActiveSpan subTask = tracer.buildSpan("subtask").startActive()) { + try (Scope subTaskScope = tracer.buildSpan("subtask").startActive()) { } } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Actor.java b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Actor.java new file mode 100644 index 00000000..d36c1885 --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Actor.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples.async_propagation; + +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.mock.MockTracer; +import io.opentracing.tag.Tags; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.Phaser; + +/** @author tylerbenson */ +public class Actor implements AutoCloseable { + private final ExecutorService executor; + private final MockTracer tracer; + private final Phaser phaser; + + public Actor(MockTracer tracer, Phaser phaser) { + // Passed along here for testing. Normally should be referenced via GlobalTracer.get(). + this.tracer = tracer; + + this.phaser = phaser; + executor = Executors.newSingleThreadExecutor(); + } + + @Override + public void close() { + executor.shutdown(); + } + + public void tell(final String message) { + final Span span = tracer.scopeManager().active().span(); + phaser.register(); + executor.submit( + new Runnable() { + @Override + public void run() { + try (Scope parent = tracer.scopeManager().activate(span)) { + try (Scope child = + tracer + .buildSpan("sent") + .asChildOf(parent.span()) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CONSUMER) + .startActive()) { + phaser.arriveAndAwaitAdvance(); // child tracer started + System.out.println("received " + message); + phaser.arriveAndAwaitAdvance(); // assert size + } + phaser.arriveAndAwaitAdvance(); // child tracer finished + phaser.arriveAndAwaitAdvance(); // assert size + } + phaser.arriveAndAwaitAdvance(); // parent tracer finished + phaser.arriveAndDeregister(); // assert size + } + }); + } + + public Future ask(final String message) { + final Span span = tracer.scopeManager().active().span(); + phaser.register(); + Future future = + executor.submit( + new Callable() { + @Override + public String call() throws Exception { + try (Scope parent = tracer.scopeManager().activate(span)) { + try (Scope child = + tracer + .buildSpan("sent") + .asChildOf(parent.span()) + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CONSUMER) + .startActive()) { + phaser.arriveAndAwaitAdvance(); // child tracer started + phaser.arriveAndAwaitAdvance(); // assert size + return "received " + message; + } finally { + phaser.arriveAndAwaitAdvance(); // child tracer finished + phaser.arriveAndAwaitAdvance(); // assert size + } + } finally { + phaser.arriveAndAwaitAdvance(); // parent tracer finished + phaser.arriveAndDeregister(); // assert size + } + } + }); + return future; + } +} diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/AsyncPropagationTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/AsyncPropagationTest.java new file mode 100644 index 00000000..1888d682 --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/AsyncPropagationTest.java @@ -0,0 +1,217 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples.async_propagation; + +import io.opentracing.Scope; +import io.opentracing.examples.AutoFinishScope; +import io.opentracing.examples.AutoFinishScopeManager; +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import io.opentracing.mock.MockTracer.Propagator; +import io.opentracing.tag.Tags; +import io.opentracing.util.ThreadLocalScopeManager; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.AtomicReference; + +import static io.opentracing.examples.TestUtils.getOneByTag; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * These tests are intended to simulate the kind of async models that are common in java async + * frameworks. + * + *

    + *
  • Actor ask/tell + *
  • Promises with callbacks + *
  • Work split by suspend/resume + *
+ * + * For improved readability, ignore the phaser lines as those are there to ensure deterministic + * execution for the tests without sleeps. + * + * @author tylerbenson + */ +public class AsyncPropagationTest { + + private final MockTracer tracer = + new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); + private final MockTracer autoTracer = + new MockTracer(new AutoFinishScopeManager(), Propagator.TEXT_MAP); + private Phaser phaser; + + @Before + public void before() { + phaser = new Phaser(); + } + + @Test + public void testActorTell() { + try (Actor actor = new Actor(tracer, phaser)) { + phaser.register(); + try (Scope parent = + tracer + .buildSpan("actorTell") + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_PRODUCER) + .withTag(Tags.COMPONENT.getKey(), "example-actor") + .startActive(false)) { + actor.tell("my message"); + } + phaser.arriveAndAwaitAdvance(); // child tracer started + assertThat(tracer.finishedSpans().size()).isEqualTo(0); + phaser.arriveAndAwaitAdvance(); // continue... + + phaser.arriveAndAwaitAdvance(); // child tracer finished + assertThat(tracer.finishedSpans().size()).isEqualTo(1); + assertThat(getOneByTag(tracer.finishedSpans(), Tags.SPAN_KIND, Tags.SPAN_KIND_CONSUMER)) + .isNotNull(); + phaser.arriveAndAwaitAdvance(); // continue... + + phaser.arriveAndAwaitAdvance(); // parent tracer finished + List finished = tracer.finishedSpans(); + phaser.arriveAndDeregister(); // continue... + + assertThat(finished.size()).isEqualTo(2); + assertThat(finished.get(0).context().traceId()) + .isEqualTo(finished.get(1).context().traceId()); + assertThat(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_CONSUMER)).isNotNull(); + assertThat(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_PRODUCER)).isNotNull(); + assertThat(tracer.scopeManager().active()).isNull(); + } + } + + @Test + public void testActorAsk() throws ExecutionException, InterruptedException { + try (Actor actor = new Actor(tracer, phaser)) { + phaser.register(); + Future future; + try (Scope parent = + tracer + .buildSpan("actorAsk") + .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_PRODUCER) + .withTag(Tags.COMPONENT.getKey(), "example-actor") + .startActive(false)) { + future = actor.ask("my message"); + } + phaser.arriveAndAwaitAdvance(); // child tracer started + assertThat(tracer.finishedSpans().size()).isEqualTo(0); + phaser.arriveAndAwaitAdvance(); // continue... + + phaser.arriveAndAwaitAdvance(); // child tracer finished + assertThat(tracer.finishedSpans().size()).isEqualTo(1); + assertThat(getOneByTag(tracer.finishedSpans(), Tags.SPAN_KIND, Tags.SPAN_KIND_CONSUMER)) + .isNotNull(); + phaser.arriveAndAwaitAdvance(); // continue... + + phaser.arriveAndAwaitAdvance(); // parent tracer finished + List finished = tracer.finishedSpans(); + phaser.arriveAndDeregister(); // continue... + + String message = future.get(); // This really should be a non-blocking callback... + assertThat(message).isEqualTo("received my message"); + assertThat(finished.size()).isEqualTo(2); + assertThat(finished.get(0).context().traceId()) + .isEqualTo(finished.get(1).context().traceId()); + assertThat(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_CONSUMER)).isNotNull(); + assertThat(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_PRODUCER)).isNotNull(); + assertThat(tracer.scopeManager().active()).isNull(); + } + } + + @Test + public void testPromiseCallback() { + phaser.register(); // register test thread + final AtomicReference successResult = new AtomicReference<>(); + final AtomicReference errorResult = new AtomicReference<>(); + try (PromiseContext context = new PromiseContext(phaser, 2)) { + try (Scope parent = + autoTracer + .buildSpan("promises") + .withTag(Tags.COMPONENT.getKey(), "example-promises") + .startActive()) { + + Promise promise1 = new Promise<>(context, autoTracer); + + promise1.onSuccess( + new Promise.SuccessCallback() { + @Override + public void accept(String s) { + successResult.set(s); + phaser.arriveAndAwaitAdvance(); // result set + } + }); + + Promise promise2 = new Promise(context, autoTracer); + + promise2.onError( + new Promise.ErrorCallback() { + @Override + public void accept(Throwable t) { + errorResult.set(t); + phaser.arriveAndAwaitAdvance(); // result set + } + }); + assertThat(autoTracer.finishedSpans().size()).isEqualTo(0); + promise1.success("success!"); + promise2.error(new Exception("some error.")); + } + + phaser.arriveAndAwaitAdvance(); // wait for results to be set + assertThat(successResult.get()).isEqualTo("success!"); + assertThat(errorResult.get()).hasMessage("some error."); + + phaser.arriveAndAwaitAdvance(); // wait for traces to be reported + List finished = autoTracer.finishedSpans(); + assertThat(finished.size()).isEqualTo(3); + assertThat(getOneByTag(finished, Tags.COMPONENT, "example-promises")).isNotNull(); + assertThat(getOneByTag(finished, Tags.COMPONENT, "example-promises").parentId()).isEqualTo(0); + long parentId = getOneByTag(finished, Tags.COMPONENT, "example-promises").context().spanId(); + assertThat(getOneByTag(finished, Tags.COMPONENT, "success")).isNotNull(); + assertThat(getOneByTag(finished, Tags.COMPONENT, "success").parentId()).isEqualTo(parentId); + assertThat(getOneByTag(finished, Tags.COMPONENT, "error")).isNotNull(); + assertThat(getOneByTag(finished, Tags.COMPONENT, "error").parentId()).isEqualTo(parentId); + } + } + + @Test + public void testContinuationInterleaving() { + SuspendResume job1 = new SuspendResume(1, tracer); + SuspendResume job2 = new SuspendResume(2, tracer); + + // Pretend that the framework is controlling actual execution here. + job1.doPart("some work for 1"); + job2.doPart("some work for 2"); + job1.doPart("other work for 1"); + job2.doPart("other work for 2"); + job2.doPart("more work for 2"); + job1.doPart("more work for 1"); + + job1.done(); + job2.done(); + + List finished = tracer.finishedSpans(); + assertThat(finished.size()).isEqualTo(2); + + assertThat(finished.get(0).operationName()).isEqualTo("job 1"); + assertThat(finished.get(1).operationName()).isEqualTo("job 2"); + + assertThat(finished.get(0).parentId()).isEqualTo(0); + assertThat(finished.get(1).parentId()).isEqualTo(0); + } +} diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Promise.java b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Promise.java new file mode 100644 index 00000000..7919d7c4 --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/Promise.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples.async_propagation; + +import io.opentracing.Scope; +import io.opentracing.examples.AutoFinishScope; +import io.opentracing.examples.AutoFinishScope.Continuation; +import io.opentracing.mock.MockTracer; +import io.opentracing.tag.Tags; + +import java.util.Collection; +import java.util.LinkedList; + +/** @author tylerbenson */ +public class Promise { + private final PromiseContext context; + private final MockTracer tracer; + private final Scope scope; + + private final Collection>> successCallbacks = new LinkedList<>(); + private final Collection> errorCallbacks = new LinkedList<>(); + + public Promise(PromiseContext context, MockTracer tracer) { + this.context = context; + + // Passed along here for testing. Normally should be referenced via GlobalTracer.get(). + this.tracer = tracer; + scope = tracer.scopeManager().active(); + } + + public void onSuccess(SuccessCallback successCallback) { + Continuation capture = ((AutoFinishScope)scope).capture(); + successCallbacks.add(new Pair<>(capture, successCallback)); + } + + public void onError(ErrorCallback errorCallback) { + Continuation capture = ((AutoFinishScope)scope).capture(); + errorCallbacks.add(new Pair<>(capture, errorCallback)); + } + + public void success(final T result) { + for (final Pair> pair : successCallbacks) { + context.submit( + new Runnable() { + @Override + public void run() { + try (Scope parent = pair.capture.activate()) { + try (Scope child = + tracer + .buildSpan("success") + .withTag(Tags.COMPONENT.getKey(), "success") + .asChildOf(parent.span()) + .startActive()) { + pair.callback.accept(result); + } + } + context.getPhaser().arriveAndAwaitAdvance(); // trace reported + } + }); + } + } + + public void error(final Throwable error) { + for (final Pair pair : errorCallbacks) { + context.submit( + new Runnable() { + @Override + public void run() { + try (Scope parent = pair.capture.activate()) { + try (Scope child = + tracer + .buildSpan("error") + .withTag(Tags.COMPONENT.getKey(), "error") + .asChildOf(parent.span()) + .startActive()) { + pair.callback.accept(error); + } + } + context.getPhaser().arriveAndAwaitAdvance(); // trace reported + } + }); + } + } + + public interface SuccessCallback { + /** @param t the result of the promise */ + void accept(T t); + } + + public interface ErrorCallback { + /** @param t the error result of the promise */ + void accept(Throwable t); + } + + private class Pair { + + final Continuation capture; + final C callback; + + public Pair(Continuation capture, C callback) { + this.capture = capture; + this.callback = callback; + } + } +} diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/PromiseContext.java b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/PromiseContext.java new file mode 100644 index 00000000..8951e0d3 --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/PromiseContext.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples.async_propagation; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Phaser; + +/** @author tylerbenson */ +public class PromiseContext implements AutoCloseable { + + private final Phaser phaser; + private final ExecutorService executor; + + public PromiseContext(Phaser phaser, int concurrency) { + this.phaser = phaser; + executor = Executors.newFixedThreadPool(concurrency); + } + + @Override + public void close() { + executor.shutdown(); + } + + public void submit(Runnable runnable) { + phaser.register(); // register the work to be done on the executor + executor.submit(runnable); + } + + public Phaser getPhaser() { + return phaser; + } +} diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/SuspendResume.java b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/SuspendResume.java new file mode 100644 index 00000000..02cb87c9 --- /dev/null +++ b/opentracing-examples/src/test/java/io/opentracing/examples/async_propagation/SuspendResume.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.examples.async_propagation; + +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.mock.MockTracer; +import io.opentracing.tag.Tags; + +/** @author tylerbenson */ +public class SuspendResume { + + private final int id; + private final MockTracer tracer; + private Span span; + + public SuspendResume(int id, MockTracer tracer) { + this.id = id; + + // Passed along here for testing. Normally should be referenced via GlobalTracer.get(). + this.tracer = tracer; + + try (Scope scope = + tracer + .buildSpan("job " + id) + .withTag(Tags.COMPONENT.getKey(), "suspend-resume") + .startActive(false)) { + span = scope.span(); + } + } + + public void doPart(String name) { + try (Scope scope = tracer.scopeManager().activate(span, false)) { + scope.span().log("part: " + name); + } + } + + public void done() { + span.finish(); + } +} diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Client.java b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Client.java index 15554a08..2954ab33 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Client.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Client.java @@ -13,7 +13,7 @@ */ package io.opentracing.examples.client_server; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.Tracer; import io.opentracing.propagation.Format.Builtin; import io.opentracing.propagation.TextMapInjectAdapter; @@ -34,11 +34,11 @@ public Client(ArrayBlockingQueue queue, Tracer tracer) { public void send() throws InterruptedException { Message message = new Message(); - try (ActiveSpan activeSpan = tracer.buildSpan("send") + try (Scope scope = tracer.buildSpan("send") .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT) .withTag(Tags.COMPONENT.getKey(), "example-client") .startActive()) { - tracer.inject(activeSpan.context(), Builtin.TEXT_MAP, new TextMapInjectAdapter(message)); + tracer.inject(scope.span().context(), Builtin.TEXT_MAP, new TextMapInjectAdapter(message)); queue.put(message); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Server.java b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Server.java index 2067ee2d..240445ea 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Server.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/Server.java @@ -13,7 +13,7 @@ */ package io.opentracing.examples.client_server; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format.Builtin; @@ -34,7 +34,7 @@ public Server(ArrayBlockingQueue queue, Tracer tracer) { private void process(Message message) { SpanContext context = tracer.extract(Builtin.TEXT_MAP, new TextMapExtractAdapter(message)); - try (ActiveSpan activeSpan = tracer.buildSpan("receive") + try (Scope scope = tracer.buildSpan("receive") .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .withTag(Tags.COMPONENT.getKey(), "example-server") .asChildOf(context).startActive()) { diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/TestClientServerTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/TestClientServerTest.java index 489b98b6..a289cc06 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/client_server/TestClientServerTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/client_server/TestClientServerTest.java @@ -17,7 +17,7 @@ import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; import io.opentracing.tag.Tags; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -36,7 +36,7 @@ public class TestClientServerTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); private final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10); private Server server; @@ -65,6 +65,6 @@ public void test() throws Exception { assertEquals(finished.get(0).context().traceId(), finished.get(1).context().traceId()); assertNotNull(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_CLIENT)); assertNotNull(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_SERVER)); - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/common_request_handler/HandlerTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/common_request_handler/HandlerTest.java index 189ae1de..3e487d4c 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/common_request_handler/HandlerTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/common_request_handler/HandlerTest.java @@ -13,12 +13,12 @@ */ package io.opentracing.examples.common_request_handler; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; import io.opentracing.tag.Tags; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.Before; import org.junit.Test; @@ -39,7 +39,7 @@ */ public class HandlerTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); private final Client client = new Client(new RequestHandler(tracer)); @@ -67,7 +67,7 @@ public void two_requests() throws Exception { assertEquals(0, finished.get(0).parentId()); assertEquals(0, finished.get(1).parentId()); - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } /** @@ -75,7 +75,7 @@ public void two_requests() throws Exception { */ @Test public void parent_not_picked_up() throws Exception { - try (ActiveSpan parent = tracer.buildSpan("parent").startActive()) { + try (Scope parent = tracer.buildSpan("parent").startActive()) { String response = client.send("no_parent").get(15, TimeUnit.SECONDS); assertEquals("no_parent:response", response); } @@ -102,8 +102,8 @@ public void parent_not_picked_up() throws Exception { @Test public void bad_solution_to_set_parent() throws Exception { Client client; - try (ActiveSpan parent = tracer.buildSpan("parent").startActive()) { - client = new Client(new RequestHandler(tracer, parent.context())); + try (Scope parent = tracer.buildSpan("parent").startActive()) { + client = new Client(new RequestHandler(tracer, parent.span().context())); String response = client.send("correct_parent").get(15, TimeUnit.SECONDS); assertEquals("correct_parent:response", response); } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/late_span_finish/LateSpanFinishTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/late_span_finish/LateSpanFinishTest.java index 9fc043a0..60260771 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/late_span_finish/LateSpanFinishTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/late_span_finish/LateSpanFinishTest.java @@ -13,12 +13,12 @@ */ package io.opentracing.examples.late_span_finish; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.Test; import java.util.List; @@ -33,7 +33,7 @@ public class LateSpanFinishTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); private final ExecutorService executor = Executors.newCachedThreadPool(); @@ -58,7 +58,7 @@ public void test() throws Exception { assertSameTrace(spans); - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } @@ -72,11 +72,10 @@ private void submitTasks(final Span parentSpan) { @Override public void run() { // Alternative to calling makeActive() is to pass it manually to asChildOf() for each created Span. - try (ActiveSpan span = tracer.makeActive(parentSpan)) { - try (ActiveSpan childSpan1 = tracer.buildSpan("task1").startActive()) { + try (Scope scope = tracer.scopeManager().activate(parentSpan, false)) { + try (Scope childScope1 = tracer.buildSpan("task1").startActive()) { sleep(55); } - span.capture(); // Workaround, prevent parentSpan from being finished here. } } }); @@ -84,11 +83,10 @@ public void run() { executor.submit(new Runnable() { @Override public void run() { - try (ActiveSpan span = tracer.makeActive(parentSpan)) { - try (ActiveSpan childSpan1 = tracer.buildSpan("task2").startActive()) { + try (Scope span = tracer.scopeManager().activate(parentSpan, false)) { + try (Scope childScope2 = tracer.buildSpan("task2").startActive()) { sleep(85); } - span.capture(); // Workaround, prevent parentSpan from being finished here. } } }); diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/listener_per_request/ListenerTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/listener_per_request/ListenerTest.java index 0d8646cb..4afd3b0e 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/listener_per_request/ListenerTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/listener_per_request/ListenerTest.java @@ -17,7 +17,7 @@ import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; import io.opentracing.tag.Tags; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.Test; import java.util.List; @@ -32,7 +32,7 @@ */ public class ListenerTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); @Test @@ -44,6 +44,6 @@ public void test() throws Exception { List finished = tracer.finishedSpans(); assertEquals(1, finished.size()); assertNotNull(getOneByTag(finished, Tags.SPAN_KIND, Tags.SPAN_KIND_CLIENT)); - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/Client.java b/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/Client.java index 0a7c9858..6be803f5 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/Client.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/Client.java @@ -13,8 +13,10 @@ */ package io.opentracing.examples.multiple_callbacks; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.Tracer; +import io.opentracing.examples.AutoFinishScope; +import io.opentracing.examples.AutoFinishScope.Continuation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,15 +38,17 @@ public Client(Tracer tracer) { this.tracer = tracer; } - public Future send(final Object message, ActiveSpan parentSpan, final long milliseconds) { - final ActiveSpan.Continuation cont = parentSpan.capture(); + public Future send(final Object message, final long milliseconds) { + Scope scope = tracer.scopeManager().active(); + final Continuation cont = ((AutoFinishScope)scope).capture(); + return executor.submit(new Callable() { @Override public Object call() throws Exception { logger.info("Child thread with message '{}' started", message); - try (ActiveSpan parentSpan = cont.activate()) { - try (ActiveSpan span = tracer.buildSpan("subtask").startActive()) { + try (Scope parentScope = cont.activate()) { + try (Scope subtaskScope = tracer.buildSpan("subtask").startActive()) { // Simulate work. sleep(milliseconds); } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/MultipleCallbacksTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/MultipleCallbacksTest.java index 92100687..517e77fd 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/MultipleCallbacksTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/multiple_callbacks/MultipleCallbacksTest.java @@ -13,11 +13,11 @@ */ package io.opentracing.examples.multiple_callbacks; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; +import io.opentracing.examples.AutoFinishScopeManager; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; -import io.opentracing.util.ThreadLocalActiveSpanSource; import org.junit.Test; import java.util.List; @@ -31,16 +31,16 @@ public class MultipleCallbacksTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new AutoFinishScopeManager(), Propagator.TEXT_MAP); @Test public void test() throws Exception { Client client = new Client(tracer); - try (ActiveSpan span = tracer.buildSpan("parent").startActive()) { - client.send("task1", span, 300); - client.send("task2", span, 200); - client.send("task3", span, 100); + try (Scope scope = tracer.buildSpan("parent").startActive()) { + client.send("task1", 300); + client.send("task2", 200); + client.send("task3", 100); } await().atMost(15, TimeUnit.SECONDS).until(finishedSpansSize(tracer), equalTo(4)); @@ -56,6 +56,6 @@ public void test() throws Exception { assertEquals(parentSpan.context().spanId(), spans.get(i).parentId()); } - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } } diff --git a/opentracing-examples/src/test/java/io/opentracing/examples/nested_callbacks/NestedCallbacksTest.java b/opentracing-examples/src/test/java/io/opentracing/examples/nested_callbacks/NestedCallbacksTest.java index d25830ba..9f74fc83 100644 --- a/opentracing-examples/src/test/java/io/opentracing/examples/nested_callbacks/NestedCallbacksTest.java +++ b/opentracing-examples/src/test/java/io/opentracing/examples/nested_callbacks/NestedCallbacksTest.java @@ -13,11 +13,12 @@ */ package io.opentracing.examples.nested_callbacks; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; +import io.opentracing.Span; import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockTracer.Propagator; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import org.junit.Test; import java.util.List; @@ -34,15 +35,15 @@ public class NestedCallbacksTest { - private final MockTracer tracer = new MockTracer(new ThreadLocalActiveSpanSource(), + private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), Propagator.TEXT_MAP); private final ExecutorService executor = Executors.newCachedThreadPool(); @Test public void test() throws Exception { - try (ActiveSpan span = tracer.buildSpan("one").startActive()) { - submitCallbacks(span); + try (Scope scope = tracer.buildSpan("one").startActive(false)) { + submitCallbacks(scope.span()); } await().atMost(15, TimeUnit.SECONDS).until(finishedSpansSize(tracer), equalTo(1)); @@ -57,30 +58,27 @@ public void test() throws Exception { assertEquals(Integer.toString(i), tags.get("key" + i)); } - assertNull(tracer.activeSpan()); + assertNull(tracer.scopeManager().active()); } - private void submitCallbacks(ActiveSpan span) { - final ActiveSpan.Continuation cont = span.capture(); + private void submitCallbacks(final Span span) { executor.submit(new Runnable() { @Override public void run() { - try (ActiveSpan span = cont.activate()) { + try (Scope scope = tracer.scopeManager().activate(span, false)) { span.setTag("key1", "1"); - final ActiveSpan.Continuation cont = span.capture(); executor.submit(new Runnable() { @Override public void run() { - try (ActiveSpan span = cont.activate()) { + try (Scope scope = tracer.scopeManager().activate(span, false)) { span.setTag("key2", "2"); - final ActiveSpan.Continuation cont = span.capture(); executor.submit(new Runnable() { @Override public void run() { - try (ActiveSpan span = cont.activate()) { + try (Scope scope = tracer.scopeManager().activate(span)) { span.setTag("key3", "3"); } } diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java index 8d01144e..b0546f46 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java @@ -13,17 +13,16 @@ */ package io.opentracing.mock; -import io.opentracing.ActiveSpan; -import io.opentracing.ActiveSpanSource; -import io.opentracing.BaseSpan; -import io.opentracing.noop.NoopActiveSpanSource; -import io.opentracing.References; +import io.opentracing.Scope; +import io.opentracing.ScopeManager; import io.opentracing.Span; +import io.opentracing.noop.NoopScopeManager; +import io.opentracing.References; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; -import io.opentracing.util.ThreadLocalActiveSpanSource; +import io.opentracing.util.ThreadLocalScopeManager; import java.util.ArrayList; import java.util.HashMap; @@ -41,26 +40,26 @@ public class MockTracer implements Tracer { private List finishedSpans = new ArrayList<>(); private final Propagator propagator; - private ActiveSpanSource spanSource; + private ScopeManager scopeManager; public MockTracer() { - this(new ThreadLocalActiveSpanSource(), Propagator.PRINTER); + this(new ThreadLocalScopeManager(), Propagator.PRINTER); } - public MockTracer(ActiveSpanSource spanSource) { - this(spanSource, Propagator.PRINTER); + public MockTracer(ScopeManager scopeManager) { + this(scopeManager, Propagator.PRINTER); } - public MockTracer(ActiveSpanSource spanSource, Propagator propagator) { + public MockTracer(ScopeManager scopeManager, Propagator propagator) { + this.scopeManager = scopeManager; this.propagator = propagator; - this.spanSource = spanSource; } /** * Create a new MockTracer that passes through any calls to inject() and/or extract(). */ public MockTracer(Propagator propagator) { - this(NoopActiveSpanSource.INSTANCE, propagator); + this(NoopScopeManager.INSTANCE, propagator); } /** @@ -89,16 +88,6 @@ public synchronized List finishedSpans() { protected void onSpanFinished(MockSpan mockSpan) { } - @Override - public ActiveSpan activeSpan() { - return spanSource.activeSpan(); - } - - @Override - public ActiveSpan makeActive(Span span) { - return spanSource.makeActive(span); - } - /** * Propagator allows the developer to intercept and verify any calls to inject() and/or extract(). * @@ -174,17 +163,13 @@ public MockSpan.MockContext extract(Format format, C carrier) { } @Override - public SpanBuilder buildSpan(String operationName) { - return new SpanBuilder(operationName); + public ScopeManager scopeManager() { + return this.scopeManager; } - private SpanContext activeSpanContext() { - ActiveSpan handle = this.spanSource.activeSpan(); - if (handle == null) { - return null; - } - - return handle.context(); + @Override + public SpanBuilder buildSpan(String operationName) { + return new SpanBuilder(operationName); } @Override @@ -219,7 +204,7 @@ public SpanBuilder asChildOf(SpanContext parent) { } @Override - public SpanBuilder asChildOf(BaseSpan parent) { + public SpanBuilder asChildOf(Span parent) { return addReference(References.CHILD_OF, parent.context()); } @@ -263,14 +248,18 @@ public SpanBuilder withStartTimestamp(long microseconds) { } @Override - public MockSpan start() { - return startManual(); + public Scope startActive() { + return MockTracer.this.scopeManager().activate(this.startManual()); } @Override - public ActiveSpan startActive() { - MockSpan span = this.startManual(); - return spanSource.makeActive(span); + public Scope startActive(boolean finishOnClose) { + return MockTracer.this.scopeManager().activate(this.startManual(), finishOnClose); + } + + @Override + public MockSpan start() { + return startManual(); } @Override @@ -279,7 +268,10 @@ public MockSpan startManual() { this.startMicros = MockSpan.nowMicros(); } if (firstParent == null && !ignoringActiveSpan) { - firstParent = (MockSpan.MockContext) activeSpanContext(); + Scope activeScope = scopeManager().active(); + if (activeScope != null) { + firstParent = (MockSpan.MockContext) activeScope.span().context(); + } } return new MockSpan(MockTracer.this, operationName, startMicros, initialTags, firstParent); } diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopActiveSpanSource.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopActiveSpanSource.java deleted file mode 100644 index ddd150ce..00000000 --- a/opentracing-noop/src/main/java/io/opentracing/noop/NoopActiveSpanSource.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2016-2017 The OpenTracing Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.opentracing.noop; - -import io.opentracing.ActiveSpan; -import io.opentracing.ActiveSpanSource; -import io.opentracing.Span; -import io.opentracing.SpanContext; - -import java.util.Map; - -public interface NoopActiveSpanSource extends ActiveSpanSource { - NoopActiveSpanSource INSTANCE = new NoopActiveSpanSourceImpl(); - - interface NoopActiveSpan extends ActiveSpan { - NoopActiveSpanSource.NoopActiveSpan INSTANCE = new NoopActiveSpanSourceImpl.NoopActiveSpanImpl(); - } - interface NoopContinuation extends ActiveSpan.Continuation { - NoopActiveSpanSource.NoopContinuation INSTANCE = new NoopActiveSpanSourceImpl.NoopContinuationImpl(); - } -} - -/** - * A noop (i.e., cheap-as-possible) implementation of an ActiveSpanSource. - */ -class NoopActiveSpanSourceImpl implements NoopActiveSpanSource { - @Override - public ActiveSpan makeActive(Span span) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public ActiveSpan activeSpan() { return null; } - - static class NoopActiveSpanImpl implements NoopActiveSpanSource.NoopActiveSpan { - @Override - public void deactivate() {} - - @Override - public Continuation capture() { - return NoopActiveSpanSource.NoopContinuation.INSTANCE; - } - - @Override - public SpanContext context() { - return NoopSpanContextImpl.INSTANCE; - } - - @Override - public void close() {} - - @Override - public NoopActiveSpan setTag(String key, String value) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan setTag(String key, boolean value) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan setTag(String key, Number value) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan log(Map fields) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan log(long timestampMicroseconds, Map fields) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan log(String event) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan log(long timestampMicroseconds, String event) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public NoopActiveSpan setBaggageItem(String key, String value) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - - @Override - public String getBaggageItem(String key) { - return null; - } - - @Override - public NoopActiveSpan setOperationName(String operationName) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - } - - static class NoopContinuationImpl implements NoopActiveSpanSource.NoopContinuation { - @Override - public ActiveSpan activate() { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } - } -} diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopScopeManager.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopScopeManager.java new file mode 100644 index 00000000..2b9ab300 --- /dev/null +++ b/opentracing-noop/src/main/java/io/opentracing/noop/NoopScopeManager.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.noop; + +import io.opentracing.Scope; +import io.opentracing.ScopeManager; +import io.opentracing.Span; + +public interface NoopScopeManager extends ScopeManager { + NoopScopeManager INSTANCE = new NoopScopeManagerImpl(); + + interface NoopScope extends Scope { + NoopScope INSTANCE = new NoopScopeManagerImpl.NoopScopeImpl(); + } +} + +/** + * A noop (i.e., cheap-as-possible) implementation of an ScopeManager. + */ +class NoopScopeManagerImpl implements NoopScopeManager { + @Override + public Scope activate(Span span) { + return NoopScope.INSTANCE; + } + + @Override + public Scope activate(Span span, boolean finishOnClose) { + return NoopScope.INSTANCE; + } + + @Override + public Scope active() { + return null; + } + + static class NoopScopeImpl implements NoopScopeManager.NoopScope { + @Override + public void close() {} + + @Override + public Span span() { + return NoopSpan.INSTANCE; + } + } +} diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpan.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpan.java index 7d503066..b720aa07 100644 --- a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpan.java +++ b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpan.java @@ -19,7 +19,7 @@ import java.util.Map; public interface NoopSpan extends Span { - static final NoopSpan INSTANCE = new NoopSpanImpl(); + NoopSpan INSTANCE = new NoopSpanImpl(); } final class NoopSpanImpl implements NoopSpan { diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java index 06129830..a114e96f 100644 --- a/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java +++ b/opentracing-noop/src/main/java/io/opentracing/noop/NoopSpanBuilder.java @@ -13,8 +13,7 @@ */ package io.opentracing.noop; -import io.opentracing.ActiveSpan; -import io.opentracing.BaseSpan; +import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; @@ -42,7 +41,7 @@ public Tracer.SpanBuilder asChildOf(SpanContext parent) { public Tracer.SpanBuilder ignoreActiveSpan() { return this; } @Override - public Tracer.SpanBuilder asChildOf(BaseSpan parent) { + public Tracer.SpanBuilder asChildOf(Span parent) { return this; } @@ -67,13 +66,18 @@ public Tracer.SpanBuilder withStartTimestamp(long microseconds) { } @Override - public Span start() { - return startManual(); + public Scope startActive() { + return NoopScopeManager.NoopScope.INSTANCE; + } + + @Override + public Scope startActive(boolean finishOnClose) { + return NoopScopeManager.NoopScope.INSTANCE; } @Override - public ActiveSpan startActive() { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; + public Span start() { + return startManual(); } @Override @@ -88,4 +92,4 @@ public Iterable> baggageItems() { @Override public String toString() { return NoopSpanBuilder.class.getSimpleName(); } -} \ No newline at end of file +} diff --git a/opentracing-noop/src/main/java/io/opentracing/noop/NoopTracer.java b/opentracing-noop/src/main/java/io/opentracing/noop/NoopTracer.java index 678a302a..f3480c1c 100644 --- a/opentracing-noop/src/main/java/io/opentracing/noop/NoopTracer.java +++ b/opentracing-noop/src/main/java/io/opentracing/noop/NoopTracer.java @@ -13,8 +13,7 @@ */ package io.opentracing.noop; -import io.opentracing.ActiveSpan; -import io.opentracing.Span; +import io.opentracing.ScopeManager; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; @@ -25,6 +24,11 @@ public interface NoopTracer extends Tracer { final class NoopTracerImpl implements NoopTracer { final static NoopTracer INSTANCE = new NoopTracerImpl(); + @Override + public ScopeManager scopeManager() { + return null; + } + @Override public SpanBuilder buildSpan(String operationName) { return NoopSpanBuilderImpl.INSTANCE; } @@ -36,15 +40,5 @@ public void inject(SpanContext spanContext, Format format, C carrier) {} @Override public String toString() { return NoopTracer.class.getSimpleName(); } - - @Override - public ActiveSpan activeSpan() { - return null; - } - - @Override - public ActiveSpan makeActive(Span span) { - return NoopActiveSpanSource.NoopActiveSpan.INSTANCE; - } } diff --git a/opentracing-util/src/main/java/io/opentracing/util/GlobalTracer.java b/opentracing-util/src/main/java/io/opentracing/util/GlobalTracer.java index 89b6f073..24ef261b 100644 --- a/opentracing-util/src/main/java/io/opentracing/util/GlobalTracer.java +++ b/opentracing-util/src/main/java/io/opentracing/util/GlobalTracer.java @@ -13,10 +13,9 @@ */ package io.opentracing.util; -import io.opentracing.ActiveSpan; +import io.opentracing.ScopeManager; import io.opentracing.noop.NoopTracer; import io.opentracing.noop.NoopTracerFactory; -import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Format; @@ -123,6 +122,11 @@ public static synchronized boolean isRegistered() { return !(GlobalTracer.tracer instanceof NoopTracer); } + @Override + public ScopeManager scopeManager() { + return tracer.scopeManager(); + } + @Override public SpanBuilder buildSpan(String operationName) { return tracer.buildSpan(operationName); @@ -142,14 +146,4 @@ public SpanContext extract(Format format, C carrier) { public String toString() { return GlobalTracer.class.getSimpleName() + '{' + tracer + '}'; } - - @Override - public ActiveSpan activeSpan() { - return tracer.activeSpan(); - } - - @Override - public ActiveSpan makeActive(Span span) { - return tracer.makeActive(span); - } } diff --git a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpan.java b/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpan.java deleted file mode 100644 index ba90ca43..00000000 --- a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalActiveSpan.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2016-2017 The OpenTracing Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package io.opentracing.util; - -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import io.opentracing.ActiveSpan; -import io.opentracing.ActiveSpanSource; -import io.opentracing.Span; -import io.opentracing.SpanContext; -import io.opentracing.Tracer; - -/** - * {@link ThreadLocalActiveSpan} is a simple {@link ActiveSpan} implementation that relies on Java's - * thread-local storage primitive. - * - * @see ActiveSpanSource - * @see Tracer#activeSpan() - */ -public class ThreadLocalActiveSpan implements ActiveSpan { - private final ThreadLocalActiveSpanSource source; - private final Span wrapped; - private final ThreadLocalActiveSpan toRestore; - private final AtomicInteger refCount; - - ThreadLocalActiveSpan(ThreadLocalActiveSpanSource source, Span wrapped, AtomicInteger refCount) { - this.source = source; - this.refCount = refCount; - this.wrapped = wrapped; - this.toRestore = source.tlsSnapshot.get(); - source.tlsSnapshot.set(this); - } - - @Override - public void deactivate() { - if (source.tlsSnapshot.get() != this) { - // This shouldn't happen if users call methods in the expected order. Bail out. - return; - } - source.tlsSnapshot.set(toRestore); - - if (0 == refCount.decrementAndGet()) { - wrapped.finish(); - } - } - - @Override - public Continuation capture() { - return new ThreadLocalActiveSpan.Continuation(); - } - - @Override - public SpanContext context() { - return wrapped.context(); - } - - @Override - public ThreadLocalActiveSpan setTag(String key, String value) { - wrapped.setTag(key, value); - return this; - } - - @Override - public ThreadLocalActiveSpan setTag(String key, boolean value) { - wrapped.setTag(key, value); - return this; - } - - @Override - public ThreadLocalActiveSpan setTag(String key, Number value) { - wrapped.setTag(key, value); - return this; - } - - @Override - public ThreadLocalActiveSpan log(Map fields) { - wrapped.log(fields); - return this; - } - - @Override - public ThreadLocalActiveSpan log(long timestampMicroseconds, Map fields) { - wrapped.log(timestampMicroseconds, fields); - return this; - } - - @Override - public ThreadLocalActiveSpan log(String event) { - wrapped.log(event); - return this; - } - - @Override - public ThreadLocalActiveSpan log(long timestampMicroseconds, String event) { - wrapped.log(timestampMicroseconds, event); - return this; - } - - @Override - public ThreadLocalActiveSpan setBaggageItem(String key, String value) { - wrapped.setBaggageItem(key, value); - return this; - } - - @Override - public String getBaggageItem(String key) { - return wrapped.getBaggageItem(key); - } - - @Override - public ThreadLocalActiveSpan setOperationName(String operationName) { - wrapped.setOperationName(operationName); - return this; - } - - @Override - public void close() { - deactivate(); - } - - @Override - public String toString() { - return wrapped.toString(); - } - - private final class Continuation implements ActiveSpan.Continuation { - Continuation() { - refCount.incrementAndGet(); - } - - @Override - public ThreadLocalActiveSpan activate() { - return new ThreadLocalActiveSpan(source, wrapped, refCount); - } - } - -} diff --git a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScope.java b/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScope.java new file mode 100644 index 00000000..8ff5d134 --- /dev/null +++ b/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScope.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.util; + +import io.opentracing.Scope; +import io.opentracing.ScopeManager; +import io.opentracing.Span; + +/** + * {@link ThreadLocalScope} is a simple {@link Scope} implementation that relies on Java's + * thread-local storage primitive. + * + * @see ScopeManager + */ +public class ThreadLocalScope implements Scope { + private final ThreadLocalScopeManager scopeManager; + private final Span wrapped; + private final boolean finishOnClose; + private final ThreadLocalScope toRestore; + + ThreadLocalScope(ThreadLocalScopeManager scopeManager, Span wrapped, boolean finishOnClose) { + this.scopeManager = scopeManager; + this.wrapped = wrapped; + this.finishOnClose = finishOnClose; + this.toRestore = scopeManager.tlsScope.get(); + scopeManager.tlsScope.set(this); + } + + @Override + public void close() { + if (scopeManager.tlsScope.get() != this) { + // This shouldn't happen if users call methods in the expected order. Bail out. + return; + } + + if (finishOnClose) + wrapped.finish(); + + scopeManager.tlsScope.set(toRestore); + } + + @Override + public Span span() { + return wrapped; + } +} diff --git a/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScopeManager.java b/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScopeManager.java new file mode 100644 index 00000000..095c4c8f --- /dev/null +++ b/opentracing-util/src/main/java/io/opentracing/util/ThreadLocalScopeManager.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016-2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.util; + +import io.opentracing.Scope; +import io.opentracing.ScopeManager; +import io.opentracing.Span; + +/** + * A simple {@link ScopeManager} implementation built on top of Java's thread-local storage primitive. + * + * @see ThreadLocalScope + */ +public class ThreadLocalScopeManager implements ScopeManager { + final ThreadLocal tlsScope = new ThreadLocal(); + + @Override + public Scope activate(Span span) { + return new ThreadLocalScope(this, span, true); + } + + @Override + public Scope activate(Span span, boolean finishOnClose) { + return new ThreadLocalScope(this, span, finishOnClose); + } + + @Override + public Scope active() { + return tlsScope.get(); + } +} diff --git a/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanSourceTest.java b/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeManagerTest.java similarity index 61% rename from opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanSourceTest.java rename to opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeManagerTest.java index 180662ee..582f685e 100644 --- a/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanSourceTest.java +++ b/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeManagerTest.java @@ -13,48 +13,50 @@ */ package io.opentracing.util; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.Span; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ThreadLocalActiveSpanSourceTest { - private ThreadLocalActiveSpanSource source; +public class ThreadLocalScopeManagerTest { + private ThreadLocalScopeManager source; + @Before public void before() throws Exception { - source = new ThreadLocalActiveSpanSource(); + source = new ThreadLocalScopeManager(); } @Test - public void missingActiveSpan() throws Exception { - ActiveSpan missingSpan = source.activeSpan(); - assertNull(missingSpan); + public void missingActiveScope() throws Exception { + Scope missingScope = source.active(); + assertNull(missingScope); } @Test - public void makeActiveSpan() throws Exception { + public void activateSpan() throws Exception { Span span = mock(Span.class); // We can't use 1.7 features like try-with-resources in this repo without meddling with pom details for tests. - ActiveSpan activeSpan = source.makeActive(span); + Scope scope = source.activate(span); try { - assertNotNull(activeSpan); - ActiveSpan otherActiveSpan = source.activeSpan(); - assertEquals(otherActiveSpan, activeSpan); + assertNotNull(scope); + Scope otherScope = source.active(); + assertEquals(otherScope, scope); } finally { - activeSpan.close(); + scope.close(); } // Make sure the Span got finish()ed. - verify(span).finish(); + verify(span, times(1)).finish(); // And now it's gone: - ActiveSpan missingSpan = source.activeSpan(); - assertNull(missingSpan); + Scope missingScope = source.active(); + assertNull(missingScope); } -} \ No newline at end of file +} diff --git a/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanTest.java b/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeTest.java similarity index 56% rename from opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanTest.java rename to opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeTest.java index 06d49093..e97c4cbd 100644 --- a/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalActiveSpanTest.java +++ b/opentracing-util/src/test/java/io/opentracing/util/ThreadLocalScopeTest.java @@ -21,49 +21,17 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import io.opentracing.ActiveSpan; +import io.opentracing.Scope; import io.opentracing.Span; import org.junit.Before; import org.junit.Test; -public class ThreadLocalActiveSpanTest { - private ThreadLocalActiveSpanSource source; +public class ThreadLocalScopeTest { + private ThreadLocalScopeManager scopeManager; @Before public void before() throws Exception { - source = new ThreadLocalActiveSpanSource(); - } - - @Test - public void continuation() throws Exception { - Span span = mock(Span.class); - - // Quasi try-with-resources (this is 1.6). - ActiveSpan activeSpan = source.makeActive(span); - ActiveSpan.Continuation continued = null; - try { - assertNotNull(activeSpan); - continued = activeSpan.capture(); - } finally { - activeSpan.close(); - } - - // Make sure the Span was not finished since there was a capture(). - verify(span, never()).finish(); - - // Activate the continuation. - try { - activeSpan = continued.activate(); - } finally { - activeSpan.close(); - } - - // Now the Span should be finished. - verify(span, times(1)).finish(); - - // And now it's no longer active. - ActiveSpan missingSpan = source.activeSpan(); - assertNull(missingSpan); + scopeManager = new ThreadLocalScopeManager(); } @Test @@ -72,21 +40,21 @@ public void implicitSpanStack() throws Exception { Span foregroundSpan = mock(Span.class); // Quasi try-with-resources (this is 1.6). - ActiveSpan backgroundActive = source.makeActive(backgroundSpan); + Scope backgroundActive = scopeManager.activate(backgroundSpan); try { assertNotNull(backgroundActive); - // Activate a new ActiveSpan on top of the background one. - ActiveSpan foregroundActive = source.makeActive(foregroundSpan); + // Activate a new Scope on top of the background one. + Scope foregroundActive = scopeManager.activate(foregroundSpan); try { - ActiveSpan shouldBeForeground = source.activeSpan(); + Scope shouldBeForeground = scopeManager.active(); assertEquals(foregroundActive, shouldBeForeground); } finally { foregroundActive.close(); } // And now the backgroundActive should be reinstated. - ActiveSpan shouldBeBackground = source.activeSpan(); + Scope shouldBeBackground = scopeManager.active(); assertEquals(backgroundActive, shouldBeBackground); } finally { backgroundActive.close(); @@ -97,7 +65,7 @@ public void implicitSpanStack() throws Exception { verify(foregroundSpan, times(1)).finish(); // And now nothing is active. - ActiveSpan missingSpan = source.activeSpan(); + Scope missingSpan = scopeManager.active(); assertNull(missingSpan); } @@ -105,9 +73,9 @@ public void implicitSpanStack() throws Exception { public void testDeactivateWhenDifferentSpanIsActive() { Span span = mock(Span.class); - ActiveSpan activeSpan = source.makeActive(span); - source.makeActive(mock(Span.class)); - activeSpan.deactivate(); + Scope active = scopeManager.activate(span); + scopeManager.activate(mock(Span.class)); + active.close(); verify(span, times(0)).finish(); }