Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ public class Agent {
}

// fields must be managed under class lock
private static ClassLoader PARENT_CLASSLOADER = null;
private static ClassLoader BOOTSTRAP_PROXY = null;
private static ClassLoader AGENT_CLASSLOADER = null;
private static ClassLoader JMXFETCH_CLASSLOADER = null;

public static void start(final Instrumentation inst, final URL bootstrapURL) {
createParentClassloader(bootstrapURL);
startDatadogAgent(inst, bootstrapURL);

final boolean appUsingCustomLogManager = isAppUsingCustomLogManager();
Expand Down Expand Up @@ -160,12 +163,38 @@ public void execute() {
}
}

private static synchronized void createParentClassloader(final URL bootstrapURL) {
if (PARENT_CLASSLOADER == null) {
try {
final Class<?> bootstrapProxyClass =
ClassLoader.getSystemClassLoader()
.loadClass("datadog.trace.bootstrap.DatadogClassLoader$BootstrapClassLoaderProxy");
final Constructor constructor = bootstrapProxyClass.getDeclaredConstructor(URL.class);
BOOTSTRAP_PROXY = (ClassLoader) constructor.newInstance(bootstrapURL);

final ClassLoader grandParent;
if (isJavaBefore9()) {
grandParent = null; // bootstrap
} else {
// platform classloader is parent of system in java 9+
grandParent = getPlatformClassLoader();
}

PARENT_CLASSLOADER = createDatadogClassLoader("shared.isolated", bootstrapURL, grandParent);
} catch (final Throwable ex) {
log.error("Throwable thrown creating parent classloader", ex);
}
}
}

private static synchronized void startDatadogAgent(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is synchronized needed for this method ? And AGENT_CLASSLOADER not volatile at the same time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. Just following the comments in the class that said access to the classloader fields need to be synchronized. I know at one point startDatadogAgent() and similar methods could have been called by multiple thread because of weird classloading situations.

I think that might have been fixed with #1084 but the synchronized attributes were never removed so I don't know.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in this case, I think synchronized or some other lock is the right choice.
If we had any reads outside, the synchronized block we also need to make the field volatile, but I don't think we do.

Since this is effectively a lazy singleton, the one other approach we could take is a holder class - like...
class AgentClassLoaderHolder {
static final DatadogClassLoader loader = ...
}

The main benefit of that approach is that the locking is handled implicitly through the JVM class init checking and gets optimized away once JIT-ted. However, we're not accessing these fields enough for that to be a significant issue.

And if there's any context that needs to be captured by the Holder class, this approach doesn't really work.

In short - for now, I think this is probably the right solution.

final Instrumentation inst, final URL bootstrapURL) {
if (AGENT_CLASSLOADER == null) {
try {
final ClassLoader agentClassLoader =
createDatadogClassLoader("agent-tooling-and-instrumentation.isolated", bootstrapURL);
createDatadogClassLoader(
"agent-tooling-and-instrumentation.isolated", bootstrapURL, PARENT_CLASSLOADER);

final Class<?> agentInstallerClass =
agentClassLoader.loadClass("datadog.trace.agent.tooling.AgentInstaller");
final Method agentInstallerMethod =
Expand Down Expand Up @@ -202,7 +231,7 @@ private static synchronized void startJmxFetch(final URL bootstrapURL) {
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
final ClassLoader jmxFetchClassLoader =
createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL);
createDatadogClassLoader("agent-jmxfetch.isolated", bootstrapURL, PARENT_CLASSLOADER);
Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
final Class<?> jmxFetchAgentClass =
jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
Expand Down Expand Up @@ -243,20 +272,16 @@ private static void setSystemPropertyDefault(final String property, final String
* @return Datadog Classloader
*/
private static ClassLoader createDatadogClassLoader(
final String innerJarFilename, final URL bootstrapURL) throws Exception {
final ClassLoader agentParent;
if (isJavaBefore9()) {
agentParent = null; // bootstrap
} else {
// platform classloader is parent of system in java 9+
agentParent = getPlatformClassLoader();
}
final String innerJarFilename, final URL bootstrapURL, final ClassLoader parent)
throws Exception {

final Class<?> loaderClass =
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.DatadogClassLoader");
final Constructor constructor =
loaderClass.getDeclaredConstructor(URL.class, String.class, ClassLoader.class);
return (ClassLoader) constructor.newInstance(bootstrapURL, innerJarFilename, agentParent);
loaderClass.getDeclaredConstructor(
URL.class, String.class, ClassLoader.class, ClassLoader.class);
return (ClassLoader)
constructor.newInstance(bootstrapURL, innerJarFilename, BOOTSTRAP_PROXY, parent);
}

private static ClassLoader getPlatformClassLoader()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class DatadogClassLoader extends URLClassLoader {
// adds a jar to the bootstrap class lookup, but not to the resource lookup.
// As a workaround, we keep a reference to the bootstrap jar
// to use only for resource lookups.
private final BootstrapClassLoaderProxy bootstrapProxy;
private final ClassLoader bootstrapProxy;
/**
* Construct a new DatadogClassLoader
*
Expand All @@ -31,14 +31,13 @@ public class DatadogClassLoader extends URLClassLoader {
* 9+.
*/
public DatadogClassLoader(
final URL bootstrapJarLocation, final String internalJarFileName, final ClassLoader parent) {
final URL bootstrapJarLocation,
final String internalJarFileName,
final ClassLoader bootstrapProxy,
final ClassLoader parent) {
super(new URL[] {}, parent);

// some tests pass null
bootstrapProxy =
bootstrapJarLocation == null
? new BootstrapClassLoaderProxy(new URL[0])
: new BootstrapClassLoaderProxy(new URL[] {bootstrapJarLocation});
this.bootstrapProxy = bootstrapProxy;

try {
// The fields of the URL are mostly dummy. InternalJarURLHandler is the only important
Expand Down Expand Up @@ -78,7 +77,7 @@ public boolean hasLoadedClass(final String className) {
return findLoadedClass(className) != null;
}

public BootstrapClassLoaderProxy getBootstrapProxy() {
public ClassLoader getBootstrapProxy() {
return bootstrapProxy;
}

Expand All @@ -93,8 +92,12 @@ public static final class BootstrapClassLoaderProxy extends URLClassLoader {
ClassLoader.registerAsParallelCapable();
}

public BootstrapClassLoaderProxy(final URL[] urls) {
super(urls, null);
public BootstrapClassLoaderProxy(final URL url) {
super(new URL[] {url}, null);
}

public BootstrapClassLoaderProxy() {
super(new URL[0], null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ class DatadogClassLoaderTest extends Specification {
def className1 = 'some/class/Name1'
def className2 = 'some/class/Name2'
final URL loc = getClass().getProtectionDomain().getCodeSource().getLocation()
final DatadogClassLoader ddLoader = new DatadogClassLoader(loc, null, null)
final DatadogClassLoader ddLoader = new DatadogClassLoader(loc,
null,
new DatadogClassLoader.BootstrapClassLoaderProxy(),
null)
final Phaser threadHoldLockPhase = new Phaser(2)
final Phaser acquireLockFromMainThreadPhase = new Phaser(2)

Expand Down
10 changes: 2 additions & 8 deletions dd-java-agent/agent-jmxfetch/agent-jmxfetch.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,12 @@ dependencies {
compile project(':dd-trace-api')
}

configurations {
// exclude bootstrap dependencies from shadowJar
runtime.exclude module: deps.opentracing
runtime.exclude module: deps.slf4j
runtime.exclude group: 'org.slf4j'
runtime.exclude group: 'io.opentracing'
}

shadowJar {
dependencies deps.sharedInverse
dependencies {
exclude(project(':dd-java-agent:agent-bootstrap'))
exclude(project(':dd-trace-api'))
exclude(dependency('org.slf4j::'))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import datadog.trace.bootstrap.DatadogClassLoader;
import datadog.trace.bootstrap.DatadogClassLoader.BootstrapClassLoaderProxy;
import java.lang.reflect.Method;
import java.net.URL;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;

Expand All @@ -15,7 +14,7 @@ public class Utils {
private static Method findLoadedClassMethod = null;

private static final BootstrapClassLoaderProxy unitTestBootstrapProxy =
new BootstrapClassLoaderProxy(new URL[0]);
new BootstrapClassLoaderProxy();

static {
try {
Expand All @@ -31,7 +30,7 @@ public static ClassLoader getAgentClassLoader() {
}

/** Return a classloader which can be used to look up bootstrap resources. */
public static BootstrapClassLoaderProxy getBootstrapProxy() {
public static ClassLoader getBootstrapProxy() {
if (getAgentClassLoader() instanceof DatadogClassLoader) {
return ((DatadogClassLoader) getAgentClassLoader()).getBootstrapProxy();
} else {
Expand Down Expand Up @@ -86,10 +85,10 @@ public static MethodDescription getMethodDefinition(

/** @return The current stack trace with multiple entries on new lines. */
public static String getStackTraceAsString() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StringBuilder stringBuilder = new StringBuilder();
String lineSeparator = System.getProperty("line.separator");
for (StackTraceElement element : stackTrace) {
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
final StringBuilder stringBuilder = new StringBuilder();
final String lineSeparator = System.getProperty("line.separator");
for (final StackTraceElement element : stackTrace) {
stringBuilder.append(element.toString());
stringBuilder.append(lineSeparator);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ClassLoaderMatcherTest extends DDSpecification {
def "skips agent classloader"() {
setup:
URL root = new URL("file://")
final URLClassLoader agentLoader = new DatadogClassLoader(root, null, null)
final URLClassLoader agentLoader = new DatadogClassLoader(root, null, new DatadogClassLoader.BootstrapClassLoaderProxy(), null)
expect:
ClassLoaderMatcher.skipClassLoader().matches(agentLoader)
}
Expand Down
113 changes: 54 additions & 59 deletions dd-java-agent/dd-java-agent.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
id "com.github.johnrengelman.shadow" version "5.2.0"
}
Expand All @@ -9,54 +11,68 @@ apply from: "${rootDir}/gradle/publish.gradle"

configurations {
shadowInclude
sharedShadowInclude
}

/*
* Include subproject's shadowJar in the dd-java-agent jar.
* Note jarname must not end with '.jar', or its classes will be on the classpath of
* the dd-java-agent jar.
* 4 shadow jars are created
* - The main "dd-java-agent" jar that also has the bootstrap project
* - 2 jars based on projects (jmxfetch, agent tooling)
* - 1 based on the shared dependencies
* This general config is shared by all of them
*/

def includeShadowJar(subproject, jarname) {
def agent_project = project
subproject.afterEvaluate {
agent_project.processResources {
from(zipTree(subproject.tasks.shadowJar.archiveFile)) {
into jarname
rename '(^.*)\\.class$', '$1.classdata'
// Rename LICENSE file since it clashes with license dir on non-case sensitive FSs (i.e. Mac)
rename '^LICENSE$', 'LICENSE.renamed'
}
}
ext.generalShadowJarConfig = {
mergeServiceFiles()

agent_project.processResources.dependsOn subproject.tasks.shadowJar
subproject.shadowJar {
mergeServiceFiles()
exclude '**/module-info.class'

exclude '**/module-info.class'
dependencies {
exclude(dependency("org.projectlombok:lombok:$versions.lombok"))
}

dependencies {
exclude(dependency("org.projectlombok:lombok:$versions.lombok"))
}
// Prevents conflict with other SLF4J instances. Important for premain.
relocate 'org.slf4j', 'datadog.slf4j'
// rewrite dependencies calling Logger.getLogger
relocate 'java.util.logging.Logger', 'datadog.trace.bootstrap.PatchLogger'

// Prevents conflict with other SLF4J instances. Important for premain.
relocate 'org.slf4j', 'datadog.slf4j'
// rewrite dependencies calling Logger.getLogger
relocate 'java.util.logging.Logger', 'datadog.trace.bootstrap.PatchLogger'
if (!project.hasProperty("disableShadowRelocate") || !disableShadowRelocate) {
// shadow OT impl to prevent casts to implementation
relocate 'datadog.trace.common', 'datadog.trace.agent.common'
relocate 'datadog.opentracing', 'datadog.trace.agent.ot'
}
}

if (!project.hasProperty("disableShadowRelocate") || !disableShadowRelocate) {
// shadow OT impl to prevent casts to implementation
relocate 'datadog.trace.common', 'datadog.trace.agent.common'
relocate 'datadog.opentracing', 'datadog.trace.agent.ot'
}
def includeShadowJar(shadowJarTask, jarname) {
project.processResources {
from(zipTree(shadowJarTask.archiveFile)) {
into jarname + '.isolated'
rename '(^.*)\\.class$', '$1.classdata'
// Rename LICENSE file since it clashes with license dir on non-case sensitive FSs (i.e. Mac)
rename '^LICENSE$', 'LICENSE.renamed'
}
}

project.processResources.dependsOn shadowJarTask
shadowJarTask.configure generalShadowJarConfig
}

includeShadowJar(project(':dd-java-agent:instrumentation'), 'agent-tooling-and-instrumentation.isolated')
includeShadowJar(project(':dd-java-agent:agent-jmxfetch'), 'agent-jmxfetch.isolated')
project(':dd-java-agent:instrumentation').afterEvaluate {
includeShadowJar(it.tasks.shadowJar, 'agent-tooling-and-instrumentation')
}
project(':dd-java-agent:agent-jmxfetch').afterEvaluate {
includeShadowJar(it.tasks.shadowJar, 'agent-jmxfetch')
}

task sharedShadowJar(type: ShadowJar) {
configurations = [project.configurations.sharedShadowInclude]
}
includeShadowJar(sharedShadowJar, 'shared')

jar {
archiveClassifier = 'unbundled'
shadowJar generalShadowJarConfig >> {
configurations = [project.configurations.shadowInclude]

archiveClassifier = ''

manifest {
attributes(
Expand All @@ -69,31 +85,6 @@ jar {
}
}

shadowJar {
configurations = [project.configurations.shadowInclude]

archiveClassifier = ''

mergeServiceFiles()

exclude '**/module-info.class'

dependencies {
exclude(dependency("org.projectlombok:lombok:$versions.lombok"))
}

// Prevents conflict with other SLF4J instances. Important for premain.
relocate 'org.slf4j', 'datadog.slf4j'
// rewrite dependencies calling Logger.getLogger
relocate 'java.util.logging.Logger', 'datadog.trace.bootstrap.PatchLogger'

if (!project.hasProperty("disableShadowRelocate") || !disableShadowRelocate) {
// shadow OT impl to prevent casts to implementation
relocate 'datadog.trace.common', 'datadog.trace.agent.common'
relocate 'datadog.opentracing', 'datadog.trace.agent.ot'
}
}

// We don't want bundled dependencies to show up in the pom.
modifyPom {
dependencies.removeAll { true }
Expand All @@ -109,7 +100,11 @@ dependencies {
testCompile deps.testLogging
testCompile deps.guava

// Includes for the top level shadow jar
shadowInclude project(path: ':dd-java-agent:agent-bootstrap')

// Includes for the shared internal shadow jar
sharedShadowInclude deps.shared
}

tasks.withType(Test).configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class UrlConnectionTest extends AgentTestRunner {

def "DatadogClassloader ClassNotFoundException doesn't create span"() {
given:
ClassLoader datadogLoader = new DatadogClassLoader(null, null, null)
ClassLoader datadogLoader = new DatadogClassLoader(null, null, new DatadogClassLoader.BootstrapClassLoaderProxy(), null)
ClassLoader childLoader = new URLClassLoader(new URL[0], datadogLoader)

when:
Expand Down
Loading