Skip to content

Commit e481471

Browse files
authored
Opentracing ScopeManager implementation based on our context (#110)
Implementation of ScopeManager based on our own AbstractThreadLocalContext class with the following advantages over the 'standard' opentracing-util ThreadLocalScopeManager: 1. Close is explicitly idempotent; closing more than once has no additional side-effects (even when finishOnClose is set to true). 2. More predictable behaviour for out-of-order closing of scopes. Although this is explicitly unsupported by the opentracing specification, we think having consistent and predictable behaviour is an advantage. 3. Support for {@link nl.talsmasoftware.context.observer.ContextObserver}. See opentracing/opentracing-java#334 why this is an advantage.
1 parent f4177c6 commit e481471

File tree

4 files changed

+421
-0
lines changed

4 files changed

+421
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2016-2019 Talsma ICT
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.talsmasoftware.context.opentracing;
17+
18+
import io.opentracing.Scope;
19+
import io.opentracing.ScopeManager;
20+
import io.opentracing.Span;
21+
import nl.talsmasoftware.context.Context;
22+
import nl.talsmasoftware.context.ContextManager;
23+
import nl.talsmasoftware.context.threadlocal.AbstractThreadLocalContext;
24+
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
/**
28+
* Our own implementation of the opentracing {@linkplain ScopeManager}.
29+
*
30+
* <p>
31+
* Manages opentracing {@linkplain Scope} and allows it to be nested within another active scope,
32+
* taking care to restore the previous value when closing an active scope.
33+
*
34+
* <p>
35+
* This manager is based on our {@linkplain nl.talsmasoftware.context.threadlocal.AbstractThreadLocalContext}
36+
* implementation. Compared to the 'standard' {@linkplain io.opentracing.util.ThreadLocalScopeManager}
37+
* this implementation has the following advantages:
38+
* <ol>
39+
* <li>Close is explicitly idempotent; closing more than once has no additional side-effects
40+
* (even when finishOnClose is set to {@code true}).</li>
41+
* <li>More predictable behaviour for out-of-order closing of scopes.
42+
* Although this is explicitly unsupported by the opentracing specification,
43+
* we think having consistent and predictable behaviour is an advantage.
44+
* <li>Support for {@link nl.talsmasoftware.context.observer.ContextObserver}.
45+
* See https://github.com/opentracing/opentracing-java/issues/334 explicitly wanting this.
46+
* </ol>
47+
*
48+
* <p>
49+
* Please note that this scope manager is not somehow automatically enabled.
50+
* You will have to provide an instance to your tracer of choice when initializing it.
51+
*
52+
* <p>
53+
* The <em>active span</em> that is automatically propagated when using this
54+
* {@code opentracing-span-propagation} library in combination with
55+
* the context aware support classes is from the registered ScopeManager
56+
* from the {@linkplain io.opentracing.util.GlobalTracer}.
57+
*
58+
* @since 1.0.6
59+
*/
60+
public class ContextScopeManager implements ScopeManager, ContextManager<Span> {
61+
/**
62+
* Makes the given span the new active span.
63+
*
64+
* @param span The span to become the active span.
65+
* @param finishSpanOnClose Whether the span should automatically finish when closing the resulting scope.
66+
* @return The new active scope (must be closed from the same thread).
67+
*/
68+
@Override
69+
public Scope activate(Span span, boolean finishSpanOnClose) {
70+
return new ThreadLocalSpanContext(getClass(), span, finishSpanOnClose);
71+
}
72+
73+
/**
74+
* The currently active {@link Scope} containing the active span {@link Scope#span()}.
75+
*
76+
* @return the active scope, or {@code null} if none could be found.
77+
*/
78+
@Override
79+
public Scope active() {
80+
return ThreadLocalSpanContext.current();
81+
}
82+
83+
/**
84+
* Initializes a new context for the given {@linkplain Span}.
85+
*
86+
* @param value The span to activate.
87+
* @return The new active 'Scope'.
88+
* @see #activate(Span, boolean)
89+
*/
90+
@Override
91+
public Context<Span> initializeNewContext(Span value) {
92+
return new ThreadLocalSpanContext(getClass(), value, false);
93+
}
94+
95+
/**
96+
* @return The active span context (this is identical to the active scope).
97+
* @see #active()
98+
*/
99+
@Override
100+
public Context<Span> getActiveContext() {
101+
return ThreadLocalSpanContext.current();
102+
}
103+
104+
/**
105+
* @return String representation for this context manager.
106+
*/
107+
public String toString() {
108+
return getClass().getSimpleName();
109+
}
110+
111+
private static final class ThreadLocalSpanContext extends AbstractThreadLocalContext<Span> implements Scope {
112+
private final AtomicBoolean finishOnClose;
113+
114+
private ThreadLocalSpanContext(Class<? extends ContextManager<? super Span>> contextManagerType, Span newValue, boolean finishOnClose) {
115+
super(contextManagerType, newValue);
116+
this.finishOnClose = new AtomicBoolean(finishOnClose);
117+
}
118+
119+
private static ThreadLocalSpanContext current() {
120+
return current(ThreadLocalSpanContext.class);
121+
}
122+
123+
@Override
124+
public Span span() {
125+
return value;
126+
}
127+
128+
@Override
129+
public void close() {
130+
super.close();
131+
if (finishOnClose.compareAndSet(true, false) && value != null) {
132+
value.finish();
133+
}
134+
}
135+
}
136+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2016-2019 Talsma ICT
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nl.talsmasoftware.context.opentracing;
17+
18+
import io.opentracing.Span;
19+
import io.opentracing.mock.MockSpan;
20+
import nl.talsmasoftware.context.ContextManager;
21+
import nl.talsmasoftware.context.observer.ContextObserver;
22+
import org.hamcrest.BaseMatcher;
23+
import org.hamcrest.Description;
24+
import org.hamcrest.Matcher;
25+
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
import static java.util.Collections.synchronizedList;
30+
31+
public class ContextScopeManagerObserver implements ContextObserver<Span> {
32+
static final List<Event> observed = synchronizedList(new ArrayList<Event>());
33+
34+
@Override
35+
public Class<? extends ContextManager<Span>> getObservedContextManager() {
36+
return ContextScopeManager.class;
37+
}
38+
39+
@Override
40+
public void onActivate(Span activatedContextValue, Span previousContextValue) {
41+
observed.add(new Event(Event.Type.ACTIVATE, activatedContextValue));
42+
}
43+
44+
@Override
45+
public void onDeactivate(Span deactivatedContextValue, Span restoredContextValue) {
46+
observed.add(new Event(Event.Type.DEACTIVATE, deactivatedContextValue));
47+
}
48+
49+
static class Event {
50+
enum Type {ACTIVATE, DEACTIVATE}
51+
52+
final Thread thread;
53+
final Type type;
54+
final Span value;
55+
56+
Event(Type type, Span value) {
57+
this.thread = Thread.currentThread();
58+
this.type = type;
59+
this.value = value;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return "Event{" + type + ", thread=" + thread.getName() + ", span=" + value + '}';
65+
}
66+
}
67+
68+
static class EventMatcher extends BaseMatcher<Event> {
69+
Thread inThread;
70+
Event.Type type;
71+
Matcher<MockSpan> spanMatcher;
72+
73+
private EventMatcher(Event.Type type, Matcher<MockSpan> spanMatcher) {
74+
this.type = type;
75+
this.spanMatcher = spanMatcher;
76+
}
77+
78+
static EventMatcher activated(Matcher<MockSpan> span) {
79+
return new EventMatcher(Event.Type.ACTIVATE, span);
80+
}
81+
82+
static EventMatcher deactivated(Matcher<MockSpan> span) {
83+
return new EventMatcher(Event.Type.DEACTIVATE, span);
84+
}
85+
86+
EventMatcher inThread(Thread thread) {
87+
EventMatcher copy = new EventMatcher(type, spanMatcher);
88+
copy.inThread = thread;
89+
return copy;
90+
}
91+
92+
@Override
93+
public boolean matches(Object actual) {
94+
if (!(actual instanceof Event)) return actual == null;
95+
Event actualEv = (Event) actual;
96+
return (inThread == null || inThread.equals(actualEv.thread))
97+
&& type.equals(actualEv.type)
98+
&& spanMatcher.matches(actualEv.value);
99+
}
100+
101+
@Override
102+
public void describeTo(Description description) {
103+
description.appendText("Event ");
104+
if (inThread != null) description.appendText("in thread ").appendText(inThread.getName());
105+
description.appendValue(type).appendText(" ");
106+
spanMatcher.describeTo(description);
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)