Skip to content

Commit 7404e98

Browse files
committed
WIP
1 parent 53cf809 commit 7404e98

14 files changed

+453
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
id("me.champeau.jmh")
3+
}
4+
5+
apply(from = "$rootDir/gradle/java.gradle")
6+
7+
jmh {
8+
version = "1.28"
9+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package datadog.apm.context;
2+
3+
import javax.annotation.Nullable;
4+
5+
/** Immutable context scoped to an execution unit. */
6+
public interface Context {
7+
8+
/** Returns the root context. */
9+
static Context root() {
10+
return ContextProvider.manager().root();
11+
}
12+
13+
/**
14+
* Returns the context attached to the current execution unit.
15+
*
16+
* @return Attached context; {@link #root()} if there is none
17+
*/
18+
static Context current() {
19+
return ContextProvider.manager().current();
20+
}
21+
22+
/**
23+
* Attaches this context to the current execution unit.
24+
*
25+
* @return Scope to be closed when the context is invalid.
26+
*/
27+
default ContextScope attach() {
28+
return ContextProvider.manager().attach(this);
29+
}
30+
31+
/**
32+
* Swaps this context with the one attached to current execution unit.
33+
*
34+
* @return Previously attached context; {@link #root()} if there was none
35+
*/
36+
default Context swap() {
37+
return ContextProvider.manager().swap(this);
38+
}
39+
40+
/**
41+
* Detaches and returns the context attached to the current execution unit.
42+
*
43+
* @return Previously attached context; {@link #root()} if there was none
44+
*/
45+
static Context detach() {
46+
return ContextProvider.manager().detach();
47+
}
48+
49+
/**
50+
* Returns the context attached to the given carrier object.
51+
*
52+
* @return Attached context; {@link #root()} if there is none
53+
*/
54+
static Context from(Object carrier) {
55+
return ContextProvider.binder().from(carrier);
56+
}
57+
58+
/** Attaches this context to the given carrier object. */
59+
default void attachTo(Object carrier) {
60+
ContextProvider.binder().attachTo(carrier, this);
61+
}
62+
63+
/**
64+
* Detaches and returns the context attached to the given carrier object.
65+
*
66+
* @return Previously attached context; {@link #root()} if there was none
67+
*/
68+
static Context detachFrom(Object carrier) {
69+
return ContextProvider.binder().detachFrom(carrier);
70+
}
71+
72+
/**
73+
* Gets the value stored in this context under the given key.
74+
*
75+
* @return Value stored under the key; {@code null} if there is no value.
76+
*/
77+
@Nullable
78+
<T> T get(ContextKey<T> key);
79+
80+
/**
81+
* Creates a new context with the given key-value mapping.
82+
*
83+
* @return New context with the key-value mapping.
84+
*/
85+
<T> Context with(ContextKey<T> key, T value);
86+
87+
/**
88+
* Creates a new context with a value that has its own implicit key.
89+
*
90+
* @return New context with the implicitly keyed value.
91+
*/
92+
default Context with(ImplicitContextKeyed value) {
93+
return value.storeInto(this);
94+
}
95+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package datadog.apm.context;
2+
3+
/** Binds context to carrier objects. */
4+
public interface ContextBinder {
5+
6+
/**
7+
* Returns the context attached to the given carrier object.
8+
*
9+
* @return Attached context; {@link Context#root()} if there is none
10+
*/
11+
Context from(Object carrier);
12+
13+
/** Attaches the given context to the given carrier object. */
14+
void attachTo(Object carrier, Context context);
15+
16+
/**
17+
* Detaches and returns the context attached to the given carrier object.
18+
*
19+
* @return Previously attached context; {@link Context#root()} if there was none
20+
*/
21+
Context detachFrom(Object carrier);
22+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package datadog.apm.context;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
/**
6+
* Key for indexing values of type {@link T} stored in a {@link Context}.
7+
*
8+
* <p>Keys are compared by identity rather than by name. Each stored context type should either
9+
* share its key for re-use or implement {@link ImplicitContextKeyed} to keep its key private.
10+
*/
11+
public final class ContextKey<T> {
12+
private static final AtomicInteger INDEX_GENERATOR = new AtomicInteger(0);
13+
14+
// internal context keys
15+
16+
private final String name;
17+
final int index;
18+
19+
private ContextKey(String name) {
20+
this.name = name;
21+
this.index = INDEX_GENERATOR.getAndIncrement();
22+
}
23+
24+
/** Creates a new key with the given name. */
25+
public static <T> ContextKey<T> named(String name) {
26+
return new ContextKey<>(name);
27+
}
28+
29+
@Override
30+
public int hashCode() {
31+
return index;
32+
}
33+
34+
@Override
35+
public boolean equals(Object o) {
36+
if (this == o) {
37+
return true;
38+
} else if (o == null || getClass() != o.getClass()) {
39+
return false;
40+
} else {
41+
return index == ((ContextKey<?>) o).index;
42+
}
43+
}
44+
45+
@Override
46+
public String toString() {
47+
return name + '@' + index;
48+
}
49+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.apm.context;
2+
3+
/** Manages context across execution units. */
4+
public interface ContextManager {
5+
6+
/** Returns the root context. */
7+
Context root();
8+
9+
/**
10+
* Returns the context attached to the current execution unit.
11+
*
12+
* @return Attached context; {@link #root()} if there is none
13+
*/
14+
Context current();
15+
16+
/**
17+
* Attaches the given context to the current execution unit.
18+
*
19+
* @return Scope to be closed when the context is invalid.
20+
*/
21+
ContextScope attach(Context context);
22+
23+
/**
24+
* Swaps the given context with the one attached to current execution unit.
25+
*
26+
* @return Previously attached context; {@link #root()} if there was none
27+
*/
28+
Context swap(Context context);
29+
30+
/**
31+
* Detaches and returns the context attached to the current execution unit.
32+
*
33+
* @return Previously attached context; {@link #root()} if there was none
34+
*/
35+
Context detach();
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package datadog.apm.context;
2+
3+
final class ContextProvider {
4+
private static final ContextManager MANAGER = new DefaultContextManager();
5+
6+
private static final ContextBinder BINDER = new DefaultContextBinder();
7+
8+
static ContextManager manager() {
9+
return MANAGER;
10+
}
11+
12+
static ContextBinder binder() {
13+
return BINDER;
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.apm.context;
2+
3+
/** Controls the validity of context attached to an execution unit. */
4+
public interface ContextScope extends AutoCloseable {
5+
6+
/** Returns the context controlled by this scope. */
7+
Context context();
8+
9+
/** Detaches the context from the execution unit. */
10+
@Override
11+
void close();
12+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package datadog.apm.context;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
import java.util.WeakHashMap;
6+
7+
final class DefaultContextBinder implements ContextBinder {
8+
private static final Map<Object, Context> tracked =
9+
Collections.synchronizedMap(new WeakHashMap<>());
10+
11+
@Override
12+
public Context from(Object carrier) {
13+
Context bound = tracked.get(carrier);
14+
return null != bound ? bound : Context.root();
15+
}
16+
17+
@Override
18+
public void attachTo(Object carrier, Context context) {
19+
tracked.put(carrier, context);
20+
}
21+
22+
@Override
23+
public Context detachFrom(Object carrier) {
24+
Context previous = tracked.remove(carrier);
25+
return null != previous ? previous : Context.root();
26+
}
27+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package datadog.apm.context;
2+
3+
final class DefaultContextManager implements ContextManager {
4+
private static final ThreadLocal<Context[]> current =
5+
ThreadLocal.withInitial(() -> new Context[1]);
6+
7+
@Override
8+
public Context root() {
9+
return RootContext.INSTANCE;
10+
}
11+
12+
@Override
13+
public Context current() {
14+
return current.get()[0];
15+
}
16+
17+
@Override
18+
public ContextScope attach(Context context) {
19+
20+
Context[] holder = current.get();
21+
Context previous = holder[0];
22+
holder[0] = context;
23+
24+
return new ContextScope() {
25+
@Override
26+
public Context context() {
27+
return context;
28+
}
29+
30+
@Override
31+
public void close() {
32+
holder[0] = previous;
33+
}
34+
};
35+
}
36+
37+
@Override
38+
public Context swap(Context context) {
39+
Context[] holder = current.get();
40+
Context previous = holder[0];
41+
holder[0] = context;
42+
return previous;
43+
}
44+
45+
@Override
46+
public Context detach() {
47+
return swap(root());
48+
}
49+
50+
@Override
51+
public Context from(Object carrier) {
52+
return root();
53+
}
54+
55+
@Override
56+
public boolean attachTo(Object carrier, Context context) {
57+
return false;
58+
}
59+
60+
@Override
61+
public Context detachFrom(Object carrier) {
62+
return root();
63+
}
64+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.apm.context;
2+
3+
/** A {@link Context} value that has its own implicit {@link ContextKey}. */
4+
public interface ImplicitContextKeyed {
5+
6+
/**
7+
* Creates a new context with this value under its chosen key.
8+
*
9+
* @return New context with the implicitly keyed value.
10+
*/
11+
Context storeInto(Context context);
12+
}

0 commit comments

Comments
 (0)