Skip to content

Commit ed27aed

Browse files
committed
Replace weak-map per context store with a single global weak-map
1 parent c180b4f commit ed27aed

File tree

10 files changed

+185
-359
lines changed

10 files changed

+185
-359
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/FieldBackedContextStore.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* {@link ContextStore} that attempts to store context in its keys by using bytecode-injected
5-
* fields. Delegates to a lazy {@link WeakMap} for keys that don't have a field for this store.
5+
* fields. Delegates to the global weak map for keys that don't have a field for this store.
66
*/
77
public final class FieldBackedContextStore implements ContextStore<Object, Object> {
88
final int storeId;
@@ -16,7 +16,7 @@ public Object get(final Object key) {
1616
if (key instanceof FieldBackedContextAccessor) {
1717
return ((FieldBackedContextAccessor) key).$get$__datadogContext$(storeId);
1818
} else {
19-
return weakStore().get(key);
19+
return GlobalWeakContextStore.weakGet(key, storeId);
2020
}
2121
}
2222

@@ -25,7 +25,7 @@ public void put(final Object key, final Object context) {
2525
if (key instanceof FieldBackedContextAccessor) {
2626
((FieldBackedContextAccessor) key).$put$__datadogContext$(storeId, context);
2727
} else {
28-
weakStore().put(key, context);
28+
GlobalWeakContextStore.weakPut(key, storeId, context);
2929
}
3030
}
3131

@@ -45,7 +45,7 @@ public Object putIfAbsent(final Object key, final Object context) {
4545
}
4646
return existingContext;
4747
} else {
48-
return weakStore().putIfAbsent(key, context);
48+
return GlobalWeakContextStore.weakPutIfAbsent(key, storeId, context);
4949
}
5050
}
5151

@@ -71,7 +71,7 @@ public Object computeIfAbsent(
7171
}
7272
return existingContext;
7373
} else {
74-
return weakStore().computeIfAbsent(key, contextFactory);
74+
return GlobalWeakContextStore.weakComputeIfAbsent(key, storeId, contextFactory);
7575
}
7676
}
7777

@@ -90,22 +90,7 @@ public Object remove(Object key) {
9090
}
9191
return existingContext;
9292
} else {
93-
return weakStore().remove(key);
93+
return GlobalWeakContextStore.weakRemove(key, storeId);
9494
}
9595
}
96-
97-
// only create WeakMap-based fall-back when we need it
98-
private volatile WeakMapContextStore<Object, Object> weakStore;
99-
private final Object synchronizationInstance = new Object();
100-
101-
WeakMapContextStore<Object, Object> weakStore() {
102-
if (null == weakStore) {
103-
synchronized (synchronizationInstance) {
104-
if (null == weakStore) {
105-
weakStore = new WeakMapContextStore<>();
106-
}
107-
}
108-
}
109-
return weakStore;
110-
}
11196
}

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/FieldBackedContextStores.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,4 @@ private static FieldBackedContextStore createStore(final int storeId) {
134134
}
135135
return store;
136136
}
137-
138-
/** Injection helper that immediately delegates to the weak-map for the given context store. */
139-
public static Object weakGet(final Object key, final int storeId) {
140-
return getContextStore(storeId).weakStore().get(key);
141-
}
142-
143-
/** Injection helper that immediately delegates to the weak-map for the given context store. */
144-
public static void weakPut(final Object key, final int storeId, final Object context) {
145-
getContextStore(storeId).weakStore().put(key, context);
146-
}
147137
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package datadog.trace.bootstrap;
2+
3+
import datadog.trace.api.Platform;
4+
import datadog.trace.bootstrap.ContextStore.KeyAwareFactory;
5+
import datadog.trace.util.AgentTaskScheduler;
6+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
7+
import java.lang.ref.Reference;
8+
import java.lang.ref.ReferenceQueue;
9+
import java.lang.ref.WeakReference;
10+
import java.util.Map;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
import java.util.concurrent.TimeUnit;
13+
14+
/**
15+
* Global weak {@link ContextStore} that acts as a fall-back when field-injection isn't possible.
16+
*/
17+
public final class GlobalWeakContextStore {
18+
19+
// global map of weak (key + store-id) wrappers mapped to context values
20+
private static final Map<Object, Object> globalMap = new ConcurrentHashMap<>();
21+
22+
// stale key wrappers that are now eligible for collection
23+
private static final ReferenceQueue<Object> staleKeys = new ReferenceQueue<>();
24+
25+
private static final long CLEAN_FREQUENCY_SECONDS = 1;
26+
27+
private static final int MAX_KEYS_CLEANED_PER_CYCLE = 1_000;
28+
29+
static {
30+
if (!Platform.isNativeImageBuilder()) {
31+
AgentTaskScheduler.get()
32+
.scheduleAtFixedRate(
33+
GlobalWeakContextStore::cleanStaleKeys,
34+
CLEAN_FREQUENCY_SECONDS,
35+
CLEAN_FREQUENCY_SECONDS,
36+
TimeUnit.SECONDS);
37+
}
38+
}
39+
40+
/** Checks for stale key wrappers and removes them from the global map. */
41+
static void cleanStaleKeys() {
42+
int count = 0;
43+
Reference<?> ref;
44+
while ((ref = staleKeys.poll()) != null) {
45+
globalMap.remove(ref);
46+
if (++count >= MAX_KEYS_CLEANED_PER_CYCLE) {
47+
break; // limit work done per call
48+
}
49+
}
50+
}
51+
52+
private GlobalWeakContextStore() {}
53+
54+
public static Object weakGet(Object key, int storeId) {
55+
return globalMap.get(new LookupKey(key, storeId));
56+
}
57+
58+
public static void weakPut(Object key, int storeId, Object context) {
59+
if (context != null) {
60+
globalMap.put(new StoreKey(key, storeId), context);
61+
} else {
62+
globalMap.remove(new LookupKey(key, storeId));
63+
}
64+
}
65+
66+
public static Object weakPutIfAbsent(Object key, int storeId, Object context) {
67+
LookupKey lookupKey = new LookupKey(key, storeId);
68+
Object existing;
69+
if (null == (existing = globalMap.get(lookupKey))) {
70+
// This whole part with using synchronized is only because
71+
// we want to avoid prematurely calling the factory if
72+
// someone else is doing a putIfAbsent at the same time.
73+
// There is still the possibility that there is a concurrent
74+
// call to put that will win, but that is indistinguishable
75+
// from the put happening right after the putIfAbsent.
76+
synchronized (key) {
77+
if (null == (existing = globalMap.get(lookupKey))) {
78+
weakPut(key, storeId, existing = context);
79+
}
80+
}
81+
}
82+
return existing;
83+
}
84+
85+
@SuppressWarnings({"rawtypes", "unchecked"})
86+
public static Object weakComputeIfAbsent(
87+
Object key, int storeId, KeyAwareFactory contextFactory) {
88+
LookupKey lookupKey = new LookupKey(key, storeId);
89+
Object existing;
90+
if (null == (existing = globalMap.get(lookupKey))) {
91+
// This whole part with using synchronized is only because
92+
// we want to avoid prematurely calling the factory if
93+
// someone else is doing a putIfAbsent at the same time.
94+
// There is still the possibility that there is a concurrent
95+
// call to put that will win, but that is indistinguishable
96+
// from the put happening right after the putIfAbsent.
97+
synchronized (key) {
98+
if (null == (existing = globalMap.get(lookupKey))) {
99+
weakPut(key, storeId, existing = contextFactory.create(key));
100+
}
101+
}
102+
}
103+
return existing;
104+
}
105+
106+
public static Object weakRemove(Object key, int storeId) {
107+
return globalMap.remove(new LookupKey(key, storeId));
108+
}
109+
110+
/** Reference key used to weakly associate a key and store-id with a context value. */
111+
static final class StoreKey extends WeakReference<Object> {
112+
final int hash;
113+
final int storeId;
114+
115+
StoreKey(Object key, int storeId) {
116+
super(key, staleKeys);
117+
this.hash = (31 * storeId) + System.identityHashCode(key);
118+
this.storeId = storeId;
119+
}
120+
121+
@Override
122+
public int hashCode() {
123+
return hash;
124+
}
125+
126+
@Override
127+
@SuppressFBWarnings("Eq") // symmetric because it mirrors LookupKey.equals
128+
public boolean equals(Object o) {
129+
if (o instanceof LookupKey) {
130+
LookupKey lookupKey = (LookupKey) o;
131+
return storeId == lookupKey.storeId && get() == lookupKey.key;
132+
} else if (o instanceof StoreKey) {
133+
StoreKey storeKey = (StoreKey) o;
134+
return storeId == storeKey.storeId && get() == storeKey.get();
135+
} else {
136+
return false;
137+
}
138+
}
139+
}
140+
141+
/** Temporary key used for lookup purposes without the reference tracking overhead. */
142+
static final class LookupKey {
143+
final Object key;
144+
final int hash;
145+
final int storeId;
146+
147+
LookupKey(Object key, int storeId) {
148+
this.key = key;
149+
this.hash = (31 * storeId) + System.identityHashCode(key);
150+
this.storeId = storeId;
151+
}
152+
153+
@Override
154+
public int hashCode() {
155+
return hash;
156+
}
157+
158+
@Override
159+
@SuppressFBWarnings("Eq") // symmetric because it mirrors StoreKey.equals
160+
public boolean equals(Object o) {
161+
if (o instanceof StoreKey) {
162+
StoreKey storeKey = (StoreKey) o;
163+
return storeId == storeKey.storeId && key == storeKey.get();
164+
} else if (o instanceof LookupKey) {
165+
LookupKey lookupKey = (LookupKey) o;
166+
return storeId == lookupKey.storeId && key == lookupKey.key;
167+
} else {
168+
return false;
169+
}
170+
}
171+
}
172+
}

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/WeakMap.java

Lines changed: 0 additions & 35 deletions
This file was deleted.

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/WeakMapContextStore.java

Lines changed: 0 additions & 92 deletions
This file was deleted.

dd-java-agent/agent-builder/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ public class AgentInstaller {
5656
static {
5757
addByteBuddyRawSetting();
5858
disableByteBuddyNexus();
59-
// register weak map supplier as early as possible
60-
WeakMaps.registerAsSupplier();
6159
circularityErrorWorkaround();
6260
}
6361

0 commit comments

Comments
 (0)