-
Notifications
You must be signed in to change notification settings - Fork 345
Add ScopeManager#activate(SpanContext) #319
base: v0.32.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,8 @@ public interface ScopeManager { | |
* This {@link Scope} instance can be accessed at any time through {@link #active()}, | ||
* in case it is not possible for the user to store it (when used through middleware | ||
* or start/finish event hooks, for example). The corresponding {@link Span} can be | ||
* accessed through {@link #activeSpan()} likewise. | ||
* accessed through {@link #activeSpan()} likewise. You can also get ahold of the | ||
* active {@link Span}'s {@link SpanContext} via {@link #activeSpanContext()}. | ||
* | ||
* <p> | ||
* Usage: | ||
|
@@ -58,12 +59,65 @@ public interface ScopeManager { | |
* } | ||
* </code></pre> | ||
* | ||
* <p> | ||
* Note: You may only activate spans when you own its life cycle. | ||
* That means you must make sure that no other thread calls {@link Span#finish()} | ||
* while the scope is still active. | ||
* If you can't guarantee that, use {@link #activate(SpanContext)} instead. | ||
* | ||
* @param span the {@link Span} that should become the {@link #activeSpan()} | ||
* @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); | ||
|
||
/** | ||
* Similar to {@link #activate(Span)} but used in cases where the thread in which the | ||
* activation is performed does not have control over the life cycle of the span. | ||
* | ||
* <p> | ||
* One example of that is when performing an activation in the {@link Runnable#run()} | ||
* method of a traced {@link Runnable} wrapper which is executed by an | ||
* {@link java.util.concurrent.ExecutorService}. | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add example in code? So far doesn't explicitly mention try/finally. I think this will reinforce it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added an example and copied over the try-with-resources paragraph |
||
* <p> | ||
* The returned {@link Scope} represents the active state for the span. | ||
* Once its active period is due, {@link Scope#close()} ought to be called. | ||
* To ease this operation, {@link Scope} supports try-with-resources. | ||
* Observe the span will not be automatically finished when {@link Scope#close()} | ||
* is called. | ||
* | ||
* <p> | ||
* This {@link Scope} instance can be accessed at any time through {@link #active()}, | ||
* in case it is not possible for the user to store it (when used through middleware | ||
* or start/finish event hooks, for example). The corresponding {@link SpanContext} can be | ||
* accessed through {@link #activeSpanContext()} likewise. | ||
* In contrast to {@link #activate(Span)}, {@link #activeSpan()} will return {@code null}. | ||
* This prevents users of the {@link #activeSpan()} API to accidentally interacting with | ||
* already {@linkplain Span#finish() finished} spans. | ||
* | ||
* Usage: | ||
* <pre><code> | ||
* Span span = tracer.buildSpan("...").start(); | ||
* try (Scope scope = tracer.scopeManager().activate(span.context())) { | ||
* span.setTag("...", "..."); | ||
* ... | ||
* } catch (Exception e) { | ||
* span.log(...); | ||
* } finally { | ||
* // Optionally finish the Span if the operation it represents | ||
* // is logically completed at this point. | ||
* span.finish(); | ||
* } | ||
* </code></pre> | ||
* | ||
* @param spanContext the {@link SpanContext} that should become the {@link #activeSpanContext()} | ||
* @see #activate(Span) | ||
* @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(SpanContext spanContext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @felixbarny specifically what I mean here is that it is strange to have something that is meant for try/finally and returns null. Wouldn't you expect a NPE here because someone looks at something made for try/finally, but returns null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Scope would always be non null. But the scope can hold either a Span or just a SpanContext. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my mistake. I misread this and I don't think others would misread it |
||
|
||
/** | ||
* Return the currently active {@link Scope} which can be used to deactivate the currently active | ||
* {@link Span}. | ||
|
@@ -84,13 +138,25 @@ public interface ScopeManager { | |
* Return the currently active {@link Span}. | ||
* | ||
* <p> | ||
* Because both {@link #active()} and {@link #activeSpan()} reference the current | ||
* active state, they both will be either null or non-null. | ||
* Note that {@link #activeSpan()} can return {@code null} while {@link #active()} is non-null | ||
* in case of a {@linkplain #activate(SpanContext) span context activation}. | ||
* | ||
* @return the {@link Span active span}, or null if none could be found. | ||
*/ | ||
Span activeSpan(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similarly prefer this to be deprecated |
||
|
||
/** | ||
* Return the currently active {@link SpanContext}, which was activated by activating either | ||
* a {@link #activate(Span) Span} or a {@link #activate(SpanContext) SpanContext}. | ||
* | ||
* <p> | ||
* Because both {@link #active()} and {@link #activeSpanContext()} reference the current | ||
* active state, they both will be either null or non-null. | ||
* | ||
* @return the {@link SpanContext active span context}, or null if none could be found. | ||
*/ | ||
SpanContext activeSpanContext(); | ||
|
||
/** | ||
* @deprecated use {@link #activate(Span)} instead. | ||
* Set the specified {@link Span} as the active instance for the current | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,11 @@ public interface Tracer { | |
*/ | ||
Span activeSpan(); | ||
|
||
/** | ||
* @return the active {@link SpanContext}. This is a shorthand for {@code Tracer.scopeManager().activeSpanContext()}. | ||
*/ | ||
SpanContext activeSpanContext(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. personally don't think these shortcuts should be here. just use the scope manager, and change docs of the activateSpan stuff to mention it internally uses the same. I don't know if Tracer.toSpan(SpanContext) has been done yet, but it would clear all of this up, possibly addressing @wu-sheng's gripe about not knowing when a span is made present on another thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the interest of scoping this pr (see what I did there?) I think it's better to move this discussion to a separate issue. I added this to be in line with the current conventions. I have not heard of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this topic is indeed separately addressable, though should be done! I may have misunderstood your SELF link issue as the same end functionality as |
||
|
||
/** | ||
* Make a {@link Span} instance active for the current context (usually a thread). | ||
* This is a shorthand for {@code Tracer.scopeManager().activate(span)}. | ||
|
@@ -41,6 +46,18 @@ public interface Tracer { | |
*/ | ||
Scope activateSpan(Span span); | ||
|
||
|
||
/** | ||
* Make a {@link SpanContext} instance active for the current context (usually a thread). | ||
* This is a shorthand for {@code Tracer.scopeManager().activate(spanContext)}. | ||
* | ||
* @see {@link ScopeManager#activate(SpanContext)} | ||
* @return a {@link Scope} instance to control the end of the active period for the {@link SpanContext}. It is a | ||
* programming error to neglect to call {@link Scope#close()} on the returned instance, | ||
* and it may lead to memory leaks as the {@link Scope} may remain in the thread-local stack. | ||
*/ | ||
Scope activateSpanContext(SpanContext spanContext); | ||
|
||
/** | ||
* Return a new SpanBuilder for a Span with the given `operationName`. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Copyright 2016-2018 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.testbed.early_span_finish; | ||
|
||
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.ThreadLocalScopeManager; | ||
import org.junit.Test; | ||
|
||
import java.util.List; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static io.opentracing.testbed.TestUtils.assertSameTrace; | ||
import static io.opentracing.testbed.TestUtils.sleep; | ||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNotNull; | ||
import static org.junit.Assert.assertNull; | ||
|
||
public class EarlySpanFinishTest { | ||
|
||
private final MockTracer tracer = new MockTracer(new ThreadLocalScopeManager(), | ||
Propagator.TEXT_MAP); | ||
private final ExecutorService executor = Executors.newCachedThreadPool(); | ||
|
||
@Test | ||
public void test() throws Exception { | ||
// Create a Span manually and use it as parent of a pair of subtasks | ||
Span parentSpan = tracer.buildSpan("parent").start(); | ||
submitTasks(parentSpan); | ||
|
||
// Early-finish the parent Span now | ||
parentSpan.finish(); | ||
|
||
// Wait for the threadpool to be done first, instead of polling/waiting | ||
executor.shutdown(); | ||
executor.awaitTermination(15, TimeUnit.SECONDS); | ||
|
||
|
||
List<MockSpan> spans = tracer.finishedSpans(); | ||
assertEquals(3, spans.size()); | ||
assertEquals("parent", spans.get(0).operationName()); | ||
assertEquals(1, spans.get(0).generatedErrors().size()); | ||
assertEquals("task1", spans.get(1).operationName()); | ||
assertEquals("task2", spans.get(2).operationName()); | ||
assertSameTrace(spans); | ||
|
||
assertNull(tracer.scopeManager().active()); | ||
} | ||
|
||
|
||
/** | ||
* Fire away a few subtasks, passing a parent Span whose lifetime | ||
* is not tied at-all to the children | ||
*/ | ||
private void submitTasks(final Span parentSpan) { | ||
|
||
executor.submit(new Runnable() { | ||
@Override | ||
public void run() { | ||
// Activating the parent span is illegal as we don't have control over its life cycle. | ||
// The caller of tracer.activeSpan() has no way of knowing whether the active span is already finished. | ||
try (Scope scope = tracer.scopeManager().activate(parentSpan)) { | ||
Span childSpan = tracer.buildSpan("task1").start(); | ||
try (Scope childScope = tracer.scopeManager().activate(childSpan)) { | ||
sleep(55); | ||
childSpan.setTag("foo", "bar"); | ||
} finally { | ||
childSpan.finish(); | ||
} | ||
assertNotNull(tracer.activeSpan()); | ||
// this fails as the parent span is already finished | ||
tracer.activeSpan().setTag("foo", "bar"); | ||
} | ||
} | ||
}); | ||
|
||
executor.submit(new Runnable() { | ||
@Override | ||
public void run() { | ||
// We don't own the lifecycle of the parent span, | ||
// therefore we must activate the span context | ||
// tracer.activeSpan() will then always return null to make the intention clear | ||
// that interacting with the parent span is not possible. | ||
// This puts the burden of being aware of the lifecycle to the person writing the instrumentation | ||
// (for example io.opentracing.contrib.concurrent.TracedRunnable) | ||
// instead of the end users, who only want to customize spans. | ||
try (Scope scope = tracer.scopeManager().activate(parentSpan.context())) { | ||
Span childSpan = tracer.buildSpan("task2").start(); | ||
try (Scope childScope = tracer.scopeManager().activate(childSpan)) { | ||
sleep(85); | ||
childSpan.setTag("foo", "bar"); | ||
} finally { | ||
childSpan.finish(); | ||
} | ||
assertNull(tracer.activeSpan()); | ||
assertNotNull(tracer.activeSpanContext()); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be nice to deprecate this, but I expect this to not be a popular suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should have a separate discussion about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be (initially) against it ;) And yeah, this can definitely happen on a different discussion.