Make FieldBackedProvider inject fields only once
MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java
@@ -7,6 +7,7 @@ import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
+import datadog.trace.agent.tooling.context.FieldBackedProvider;
import datadog.trace.api.Config;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
@@ -53,6 +54,8 @@ public class AgentInstaller {
addByteBuddyRawSetting();
+ FieldBackedProvider.resetContextMatchers();
+
AgentBuilder agentBuilder =
new AgentBuilder.Default()
.disableClassFormatChanges()

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/context/FieldBackedProvider.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -342,12 +343,46 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
};
}
+ /*
+ Set of pairs (context holder, context class) for which we have matchers installed.
+ We use this to make sure we do not install matchers repeatedly for cases when same
+ context class is used by multiple instrumentations.
+ */
+ private static final Set<Map.Entry<String, String>> INSTALLED_CONTEXT_MATCHERS = new HashSet<>();
+
+ /** Clear set that prevents multiple matchers for same context class */
+ public static void resetContextMatchers() {
+ synchronized (INSTALLED_CONTEXT_MATCHERS) {
+ INSTALLED_CONTEXT_MATCHERS.clear();
+ }
+ }
+
@Override
public AgentBuilder.Identified.Extendable additionalInstrumentation(
AgentBuilder.Identified.Extendable builder) {
if (fieldInjectionEnabled) {
for (final Map.Entry<String, String> entry : instrumenter.contextStore().entrySet()) {
+ /*
+ * For each context store defined in a current instrumentation we create an agent builder
+ * that injects necessary fields.
+ * Note: this synchronization should not have any impact on performance
+ * since this is done when agent builder is being made, it doesn't affect actual
+ * class transformation.
+ */
+ synchronized (INSTALLED_CONTEXT_MATCHERS) {
+ // FIXME: This makes an assumption that class loader matchers for instrumenters that use
+ // same context classes should be the same - which seems reasonable, but is not checked.
+ // Addressing this properly requires some notion of 'compound intrumenters' which we
+ // currently do not have.
+ if (INSTALLED_CONTEXT_MATCHERS.contains(entry)) {
+ log.debug("Skipping builder for {} {}", instrumenter.getClass().getName(), entry);
+ continue;
+ }
+
+ log.debug("Making builder for {} {}", instrumenter.getClass().getName(), entry);
+ INSTALLED_CONTEXT_MATCHERS.add(entry);
+
/*
* For each context store defined in a current instrumentation we create an agent builder
* that injects necessary fields.
@@ -364,9 +399,9 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
.transform(AgentBuilder.Transformer.NoOp.INSTANCE);
/*
- * We inject helpers here as well as when instrumentation is applied to ensure that helpers
- * are present even if instrumented classes are not loaded, but classes with state fields
- * added are loaded (e.g. sun.net.www.protocol.https.HttpsURLConnectionImpl).
+ * We inject helpers here as well as when instrumentation is applied to ensure that
+ * helpers are present even if instrumented classes are not loaded, but classes with state
+ * fields added are loaded (e.g. sun.net.www.protocol.https.HttpsURLConnectionImpl).
*/
builder = injectHelpersIntoBootstrapClassloader(builder);
@@ -376,6 +411,7 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
getFieldInjectionVisitor(entry.getKey(), entry.getValue())));
}
}
+ }
return builder;
}

Make FieldBackedProvider inject fields only once