Skip to content

Commit c083482

Browse files
Introduce a shared context component, independent of tracing.
Co-authored-by: Bruce Bujon <[email protected]>
1 parent 90899e0 commit c083482

19 files changed

+954
-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: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package datadog.context;
2+
3+
import static datadog.context.ContextProviders.binder;
4+
import static datadog.context.ContextProviders.manager;
5+
6+
import javax.annotation.Nullable;
7+
8+
/** Immutable context scoped to an execution unit or carrier object. */
9+
public interface Context {
10+
11+
/** Returns the root context. */
12+
static Context root() {
13+
return manager().root();
14+
}
15+
16+
/**
17+
* Returns the context attached to the current execution unit.
18+
*
19+
* @return Attached context; {@link #root()} if there is none
20+
*/
21+
static Context current() {
22+
return manager().current();
23+
}
24+
25+
/**
26+
* Attaches this context to the current execution unit.
27+
*
28+
* @return Scope to be closed when the context is invalid.
29+
*/
30+
default ContextScope attach() {
31+
return manager().attach(this);
32+
}
33+
34+
/**
35+
* Swaps this context with the one attached to current execution unit.
36+
*
37+
* @return Previously attached context; {@link #root()} if there was none
38+
*/
39+
default Context swap() {
40+
return manager().swap(this);
41+
}
42+
43+
/**
44+
* Detaches the context attached to the current execution unit, leaving it context-less.
45+
*
46+
* <p>WARNING: prefer {@link ContextScope#close()} to properly restore the surrounding context.
47+
*
48+
* @return Previously attached context; {@link #root()} if there was none
49+
*/
50+
static Context detach() {
51+
return manager().detach();
52+
}
53+
54+
/**
55+
* Returns the context attached to the given carrier object.
56+
*
57+
* @return Attached context; {@link #root()} if there is none
58+
*/
59+
static Context from(Object carrier) {
60+
return binder().from(carrier);
61+
}
62+
63+
/** Attaches this context to the given carrier object. */
64+
default void attachTo(Object carrier) {
65+
binder().attachTo(carrier, this);
66+
}
67+
68+
/**
69+
* Detaches the context attached to the given carrier object, leaving it context-less.
70+
*
71+
* @return Previously attached context; {@link #root()} if there was none
72+
*/
73+
static Context detachFrom(Object carrier) {
74+
return binder().detachFrom(carrier);
75+
}
76+
77+
/**
78+
* Gets the value stored in this context under the given key.
79+
*
80+
* @return Value stored under the key; {@code null} if there is no value.
81+
*/
82+
@Nullable
83+
<T> T get(ContextKey<T> key);
84+
85+
/**
86+
* Creates a new context with the given key-value mapping.
87+
*
88+
* @return New context with the key-value mapping.
89+
*/
90+
<T> Context with(ContextKey<T> key, T value);
91+
92+
/**
93+
* Creates a new context with a value that has its own implicit key.
94+
*
95+
* @return New context with the implicitly keyed value.
96+
*/
97+
default Context with(ImplicitContextKeyed value) {
98+
return value.storeInto(this);
99+
}
100+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datadog.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 the context attached to the given carrier object, leaving it context-less.
18+
*
19+
* @return Previously attached context; {@link Context#root()} if there was none
20+
*/
21+
Context detachFrom(Object carrier);
22+
23+
/** Requests use of a custom {@link ContextBinder}. */
24+
static void register(ContextBinder binder) {
25+
ContextProviders.customBinder = binder;
26+
}
27+
28+
final class Provided {
29+
static final ContextBinder INSTANCE =
30+
null != ContextProviders.customBinder
31+
? ContextProviders.customBinder
32+
: new WeakMapContextBinder();
33+
}
34+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package datadog.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 NEXT_INDEX = new AtomicInteger(0);
13+
14+
private final String name;
15+
final int index;
16+
17+
private ContextKey(String name) {
18+
this.name = name;
19+
this.index = NEXT_INDEX.getAndIncrement();
20+
}
21+
22+
/** Creates a new key with the given name. */
23+
public static <T> ContextKey<T> named(String name) {
24+
return new ContextKey<>(name);
25+
}
26+
27+
@Override
28+
public int hashCode() {
29+
return index;
30+
}
31+
32+
@Override
33+
public boolean equals(Object o) {
34+
if (this == o) {
35+
return true;
36+
} else if (o == null || getClass() != o.getClass()) {
37+
return false;
38+
} else {
39+
return index == ((ContextKey<?>) o).index;
40+
}
41+
}
42+
43+
@Override
44+
public String toString() {
45+
return name;
46+
}
47+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package datadog.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 the context attached to the current execution unit, leaving it context-less.
32+
*
33+
* <p>WARNING: prefer {@link ContextScope#close()} to properly restore the surrounding context.
34+
*
35+
* @return Previously attached context; {@link #root()} if there was none
36+
*/
37+
Context detach();
38+
39+
/** Requests use of a custom {@link ContextManager}. */
40+
static void register(ContextManager manager) {
41+
ContextProviders.customManager = manager;
42+
}
43+
44+
final class Provided {
45+
static final ContextManager INSTANCE =
46+
null != ContextProviders.customManager
47+
? ContextProviders.customManager
48+
: new ThreadLocalContextManager();
49+
}
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package datadog.context;
2+
3+
/** Provides {@link ContextManager} and {@link ContextBinder} implementations. */
4+
final class ContextProviders {
5+
6+
static volatile ContextManager customManager;
7+
static volatile ContextBinder customBinder;
8+
9+
static ContextManager manager() {
10+
return ContextManager.Provided.INSTANCE; // may be overridden by instrumentation
11+
}
12+
13+
static ContextBinder binder() {
14+
return ContextBinder.Provided.INSTANCE; // may be overridden by instrumentation
15+
}
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package datadog.context;
2+
3+
/** {@link Context} containing no values. */
4+
final class EmptyContext implements Context {
5+
static final Context INSTANCE = new EmptyContext();
6+
7+
@Override
8+
public <T> T get(ContextKey<T> key) {
9+
return null;
10+
}
11+
12+
@Override
13+
public <T> Context with(ContextKey<T> key, T value) {
14+
return new SingleContext(key.index, value);
15+
}
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.context;
2+
3+
/** {@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+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package datadog.context;
2+
3+
import static java.lang.Math.max;
4+
import static java.util.Arrays.copyOfRange;
5+
6+
import java.util.Arrays;
7+
8+
/** {@link Context} containing many values. */
9+
final class IndexedContext implements Context {
10+
private final Object[] store;
11+
12+
IndexedContext(Object[] store) {
13+
this.store = store;
14+
}
15+
16+
@Override
17+
@SuppressWarnings("unchecked")
18+
public <T> T get(ContextKey<T> key) {
19+
int index = key.index;
20+
return index < store.length ? (T) store[index] : null;
21+
}
22+
23+
@Override
24+
public <T> Context with(ContextKey<T> key, T value) {
25+
int index = key.index;
26+
Object[] newStore = copyOfRange(store, 0, max(store.length, index + 1));
27+
newStore[index] = value;
28+
29+
return new IndexedContext(newStore);
30+
}
31+
32+
@Override
33+
public boolean equals(Object o) {
34+
if (this == o) return true;
35+
if (o == null || getClass() != o.getClass()) return false;
36+
IndexedContext that = (IndexedContext) o;
37+
return Arrays.equals(store, that.store);
38+
}
39+
40+
@Override
41+
public int hashCode() {
42+
int result = 31;
43+
result = 31 * result + Arrays.hashCode(store);
44+
return result;
45+
}
46+
}

0 commit comments

Comments
 (0)