Skip to content

Commit 07a1165

Browse files
committed
Replace weak-lock-free library with custom global weak map
1 parent 08f91e1 commit 07a1165

File tree

9 files changed

+182
-326
lines changed

9 files changed

+182
-326
lines changed

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

Lines changed: 5 additions & 5 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 store 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;
@@ -94,15 +94,15 @@ public Object remove(Object key) {
9494
}
9595
}
9696

97-
// only create WeakMap-based fall-back when we need it
98-
private volatile WeakMapContextStore<Object, Object> weakStore;
97+
// only create global weak store fall-back when we need it
98+
private volatile ContextStore<Object, Object> weakStore;
9999
private final Object synchronizationInstance = new Object();
100100

101-
WeakMapContextStore<Object, Object> weakStore() {
101+
ContextStore<Object, Object> weakStore() {
102102
if (null == weakStore) {
103103
synchronized (synchronizationInstance) {
104104
if (null == weakStore) {
105-
weakStore = new WeakMapContextStore<>();
105+
weakStore = new GlobalWeakContextStore<>(storeId);
106106
}
107107
}
108108
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package datadog.trace.bootstrap;
2+
3+
import datadog.trace.api.Platform;
4+
import datadog.trace.util.AgentTaskScheduler;
5+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6+
import java.lang.ref.Reference;
7+
import java.lang.ref.ReferenceQueue;
8+
import java.lang.ref.WeakReference;
9+
import java.util.Map;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
import java.util.concurrent.TimeUnit;
12+
13+
/**
14+
* Global weak {@link ContextStore} that acts as a fall-back when field-injection isn't possible.
15+
*/
16+
@SuppressWarnings("unchecked")
17+
final class GlobalWeakContextStore<K, V> implements ContextStore<K, V> {
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 final int storeId;
53+
54+
GlobalWeakContextStore(int storeId) {
55+
this.storeId = storeId;
56+
}
57+
58+
@Override
59+
public V get(K key) {
60+
return (V) globalMap.get(new LookupKey(key, storeId));
61+
}
62+
63+
@Override
64+
public void put(K key, V context) {
65+
globalMap.put(new StoreKey(key, storeId), context);
66+
}
67+
68+
@Override
69+
public V putIfAbsent(K key, V context) {
70+
LookupKey lookup = new LookupKey(key, storeId);
71+
Object existing;
72+
if (null == (existing = globalMap.get(lookup))) {
73+
// This whole part with using synchronized is only because
74+
// we want to avoid prematurely calling the factory if
75+
// someone else is doing a putIfAbsent at the same time.
76+
// There is still the possibility that there is a concurrent
77+
// call to put that will win, but that is indistinguishable
78+
// from the put happening right after the putIfAbsent.
79+
synchronized (key) {
80+
if (null == (existing = globalMap.get(lookup))) {
81+
globalMap.put(new StoreKey(key, storeId), existing = context);
82+
}
83+
}
84+
}
85+
return (V) existing;
86+
}
87+
88+
@Override
89+
public V putIfAbsent(K key, Factory<V> contextFactory) {
90+
return computeIfAbsent(key, contextFactory);
91+
}
92+
93+
@Override
94+
public V computeIfAbsent(K key, KeyAwareFactory<? super K, V> contextFactory) {
95+
LookupKey lookup = new LookupKey(key, storeId);
96+
Object existing;
97+
if (null == (existing = globalMap.get(lookup))) {
98+
// This whole part with using synchronized is only because
99+
// we want to avoid prematurely calling the factory if
100+
// someone else is doing a putIfAbsent at the same time.
101+
// There is still the possibility that there is a concurrent
102+
// call to put that will win, but that is indistinguishable
103+
// from the put happening right after the putIfAbsent.
104+
synchronized (key) {
105+
if (null == (existing = globalMap.get(lookup))) {
106+
globalMap.put(new StoreKey(key, storeId), existing = contextFactory.create(key));
107+
}
108+
}
109+
}
110+
return (V) existing;
111+
}
112+
113+
@Override
114+
public V remove(K key) {
115+
return (V) globalMap.remove(new LookupKey(key, storeId));
116+
}
117+
118+
/** Reference key used to weakly associate a key and store-id with a context value. */
119+
static final class StoreKey extends WeakReference<Object> {
120+
final int hash;
121+
final int storeId;
122+
123+
StoreKey(Object key, int storeId) {
124+
super(key, staleKeys);
125+
this.hash = (31 * storeId) + System.identityHashCode(key);
126+
this.storeId = storeId;
127+
}
128+
129+
@Override
130+
public int hashCode() {
131+
return hash;
132+
}
133+
134+
@Override
135+
@SuppressFBWarnings("Eq") // symmetric because it mirrors LookupKey.equals
136+
public boolean equals(Object o) {
137+
if (o instanceof LookupKey) {
138+
return storeId == ((LookupKey) o).storeId && get() == ((LookupKey) o).key;
139+
} else if (o instanceof StoreKey) {
140+
return storeId == ((StoreKey) o).storeId && get() == ((StoreKey) o).get();
141+
} else {
142+
return false;
143+
}
144+
}
145+
}
146+
147+
/** Temporary key used for lookup purposes without the reference tracking overhead. */
148+
static final class LookupKey {
149+
final Object key;
150+
final int hash;
151+
final int storeId;
152+
153+
LookupKey(Object key, int storeId) {
154+
this.key = key;
155+
this.hash = (31 * storeId) + System.identityHashCode(key);
156+
this.storeId = storeId;
157+
}
158+
159+
@Override
160+
public int hashCode() {
161+
return hash;
162+
}
163+
164+
@Override
165+
@SuppressFBWarnings("Eq") // symmetric because it mirrors StoreKey.equals
166+
public boolean equals(Object o) {
167+
if (o instanceof StoreKey) {
168+
return storeId == ((StoreKey) o).storeId && key == ((StoreKey) o).get();
169+
} else if (o instanceof LookupKey) {
170+
return storeId == ((LookupKey) o).storeId && key == ((LookupKey) o).key;
171+
} else {
172+
return false;
173+
}
174+
}
175+
}
176+
}

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

dd-java-agent/agent-tooling/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ dependencies {
4646
compileOnly project(':dd-java-agent:agent-jmxfetch')
4747
compileOnly project(':dd-java-agent:agent-profiling')
4848
compileOnly project(':dd-java-agent:agent-profiling:profiling-controller')
49-
api group: 'com.blogspot.mydailyjava', name: 'weak-lock-free', version: '0.17'
5049
api libs.bytebuddy
5150
api libs.bytebuddyagent
5251
implementation group: 'net.java.dev.jna', name: 'jna', version: '5.8.0'

0 commit comments

Comments
 (0)