Add some integration tests to check ByteBuddy's behavoir on class loading and parsing
MODIFY
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/integration/classloading/ClassLoadingTest.groovy
package datadog.trace.agent.integration.classloading
+import datadog.test.ClassToInstrument
+import datadog.test.ClassToInstrumentChild
import datadog.trace.agent.test.IntegrationTestUtils
import datadog.trace.api.Trace
import spock.lang.Specification
+import java.lang.ref.WeakReference
+
import static datadog.trace.agent.test.IntegrationTestUtils.createJarWithClasses
class ClassLoadingTest extends Specification {
+ final URL[] classpath = [createJarWithClasses(ClassToInstrument, ClassToInstrumentChild, Trace)]
+
/** Assert that we can instrument classloaders which cannot resolve agent advice classes. */
def "instrument classloader without agent classes"() {
setup:
- final URL[] classpath = [createJarWithClasses(ClassToInstrument, Trace)]
final URLClassLoader loader = new URLClassLoader(classpath, (ClassLoader) null)
when:
@@ -25,6 +30,61 @@ class ClassLoadingTest extends Specification {
instrumentedClass.getClassLoader() == loader
}
+ def "make sure ByteBuddy does not hold strong references to ClassLoader"() {
+ setup:
+ final URLClassLoader loader = new URLClassLoader(classpath, (ClassLoader) null)
+ final WeakReference<URLClassLoader> ref = new WeakReference<>(loader)
+
+ when:
+ loader.loadClass(ClassToInstrument.getName())
+ loader = null
+
+ IntegrationTestUtils.awaitGC()
+
+ then:
+ null == ref.get()
+ }
+
+ // We are doing this because Grovy cannot properly resolve constructor argument types in anonymous classes
+ static class CountingClassLoader extends URLClassLoader {
+ public int count = 0
+
+ CountingClassLoader(URL[] urls) {
+ super(urls, (ClassLoader) null)
+ }
+
+ @Override
+ URL getResource(String name) {
+ count++
+ }
+ }
+
+ def "make sure that ByteBuddy reads classes's bytes only once"() {
+ setup:
+ final CountingClassLoader loader = new CountingClassLoader(classpath)
+
+ when:
+ loader.loadClass(ClassToInstrument.getName())
+ loader.loadClass(ClassToInstrumentChild.getName())
+
+ then:
+ loader.count == 2
+ }
+
+ def "make sure that ByteBuddy doesn't resue cached type descriptions between different classloaders"() {
+ setup:
+ final CountingClassLoader loader1 = new CountingClassLoader(classpath)
+ final CountingClassLoader loader2 = new CountingClassLoader(classpath)
+
+ when:
+ loader1.loadClass(ClassToInstrument.getName())
+ loader2.loadClass(ClassToInstrument.getName())
+
+ then:
+ loader1.count == 1
+ loader2.count == 1
+ }
+
def "can find bootstrap resources"() {
expect:
IntegrationTestUtils.getAgentClassLoader().getResources('datadog/trace/api/Trace.class') != null

DELETE
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/integration/classloading/ClassToInstrument.java
-package datadog.trace.agent.integration.classloading;
-
-import datadog.trace.api.Trace;
-
-class ClassToInstrument {
- @Trace
- public static void someMethod() {}
-}

ADD
dd-java-agent-ittests/src/test/java/datadog/test/ClassToInstrument.java
+package datadog.test;
+
+import datadog.trace.api.Trace;
+
+/** Note: this has to stay in 'datadog.test' package to be considered for instrumentation */
+public class ClassToInstrument {
+ @Trace
+ public static void someMethod() {}
+}

ADD
dd-java-agent-ittests/src/test/java/datadog/test/ClassToInstrumentChild.java
+package datadog.test;
+
+/** Note: this has to stay in 'datadog.test' package to be considered for instrumentation */
+public class ClassToInstrumentChild extends ClassToInstrument {}

MODIFY
dd-java-agent-ittests/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
dd-java-agent-ittests/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
@@ -7,6 +7,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.UUID;
@@ -156,4 +157,14 @@ public class IntegrationTestUtils {
.getField("AGENT_PACKAGE_PREFIXES");
return (String[]) f.get(null);
}
+
+ public static void awaitGC() {
+ System.gc(); // For good measure.
+ Object obj = new Object();
+ final WeakReference ref = new WeakReference<>(obj);
+ obj = null;
+ while (ref.get() != null) {
+ System.gc();
+ }
+ }
}

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Instrumenter.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Instrumenter.java
@@ -51,7 +51,7 @@ public interface Instrumenter {
protected final boolean enabled;
public Default(final String instrumentationName, final String... additionalNames) {
- this.instrumentationNames = new HashSet<>(Arrays.asList(additionalNames));
+ instrumentationNames = new HashSet<>(Arrays.asList(additionalNames));
instrumentationNames.add(instrumentationName);
instrumentationPrimaryName = instrumentationName;
@@ -131,7 +131,7 @@ public interface Instrumenter {
log.debug(
"Instrumentation muzzled: {} -- {} on {}",
instrumentationPrimaryName,
- this.getClass().getName(),
+ getClass().getName(),
classLoader);
}
for (final Reference.Mismatch mismatch : mismatches) {

Add some integration tests to check ByteBuddy's behavoir on class loading and parsing