Merge pull request #1285 from DataDog/dougqh/jar-file-collisions
Avoid temporary Jar file collisions
This commit is contained in:
commit
3ff5b99cd6
|
@ -7,6 +7,7 @@ import datadog.trace.bootstrap.WeakMap;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.security.SecureClassLoader;
|
import java.security.SecureClassLoader;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -33,6 +34,8 @@ public class HelperInjector implements Transformer {
|
||||||
private static final ClassLoader BOOTSTRAP_CLASSLOADER_PLACEHOLDER =
|
private static final ClassLoader BOOTSTRAP_CLASSLOADER_PLACEHOLDER =
|
||||||
new SecureClassLoader(null) {};
|
new SecureClassLoader(null) {};
|
||||||
|
|
||||||
|
private final String requestingName;
|
||||||
|
|
||||||
private final Set<String> helperClassNames;
|
private final Set<String> helperClassNames;
|
||||||
private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>();
|
private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
@ -48,21 +51,26 @@ public class HelperInjector implements Transformer {
|
||||||
* provided. This is important if there is interdependency between helper classes that
|
* provided. This is important if there is interdependency between helper classes that
|
||||||
* requires them to be injected in a specific order.
|
* requires them to be injected in a specific order.
|
||||||
*/
|
*/
|
||||||
public HelperInjector(final String... helperClassNames) {
|
public HelperInjector(final String requestingName, final String... helperClassNames) {
|
||||||
|
this.requestingName = requestingName;
|
||||||
|
|
||||||
this.helperClassNames = new LinkedHashSet<>(Arrays.asList(helperClassNames));
|
this.helperClassNames = new LinkedHashSet<>(Arrays.asList(helperClassNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
public HelperInjector(final Map<String, byte[]> helperMap) {
|
public HelperInjector(final String requestingName, final Map<String, byte[]> helperMap) {
|
||||||
|
this.requestingName = requestingName;
|
||||||
|
|
||||||
helperClassNames = helperMap.keySet();
|
helperClassNames = helperMap.keySet();
|
||||||
dynamicTypeMap.putAll(helperMap);
|
dynamicTypeMap.putAll(helperMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HelperInjector forDynamicTypes(final Collection<DynamicType.Unloaded<?>> helpers) {
|
public static HelperInjector forDynamicTypes(
|
||||||
|
final String requestingName, final Collection<DynamicType.Unloaded<?>> helpers) {
|
||||||
final Map<String, byte[]> bytes = new HashMap<>(helpers.size());
|
final Map<String, byte[]> bytes = new HashMap<>(helpers.size());
|
||||||
for (final DynamicType.Unloaded<?> helper : helpers) {
|
for (final DynamicType.Unloaded<?> helper : helpers) {
|
||||||
bytes.put(helper.getTypeDescription().getName(), helper.getBytes());
|
bytes.put(helper.getTypeDescription().getName(), helper.getBytes());
|
||||||
}
|
}
|
||||||
return new HelperInjector(bytes);
|
return new HelperInjector(requestingName, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, byte[]> getHelperMap() throws IOException {
|
private Map<String, byte[]> getHelperMap() throws IOException {
|
||||||
|
@ -101,14 +109,9 @@ public class HelperInjector implements Transformer {
|
||||||
final Map<String, byte[]> classnameToBytes = getHelperMap();
|
final Map<String, byte[]> classnameToBytes = getHelperMap();
|
||||||
final Map<String, Class<?>> classes;
|
final Map<String, Class<?>> classes;
|
||||||
if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) {
|
if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) {
|
||||||
classes =
|
classes = injectBootstrapClassLoader(classnameToBytes);
|
||||||
ClassInjector.UsingInstrumentation.of(
|
|
||||||
new File(System.getProperty("java.io.tmpdir")),
|
|
||||||
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
|
|
||||||
AgentInstaller.getInstrumentation())
|
|
||||||
.injectRaw(classnameToBytes);
|
|
||||||
} else {
|
} else {
|
||||||
classes = new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
|
classes = injectClassLoader(classLoader, classnameToBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All datadog helper classes are in the unnamed module
|
// All datadog helper classes are in the unnamed module
|
||||||
|
@ -125,8 +128,9 @@ public class HelperInjector implements Transformer {
|
||||||
: classLoader.getClass().getName();
|
: classLoader.getClass().getName();
|
||||||
|
|
||||||
log.error(
|
log.error(
|
||||||
"Error preparing helpers for {}. Failed to inject helper classes into instance {} of type {}",
|
"Error preparing helpers while processing {} for {}. Failed to inject helper classes into instance {} of type {}",
|
||||||
typeDescription,
|
typeDescription,
|
||||||
|
requestingName,
|
||||||
classLoader,
|
classLoader,
|
||||||
classLoaderType,
|
classLoaderType,
|
||||||
e);
|
e);
|
||||||
|
@ -141,6 +145,31 @@ public class HelperInjector implements Transformer {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> injectBootstrapClassLoader(
|
||||||
|
final Map<String, byte[]> classnameToBytes) throws IOException {
|
||||||
|
// Mar 2020: Since we're proactively cleaning up tempDirs, we cannot share dirs per thread.
|
||||||
|
// If this proves expensive, we could do a per-process tempDir with
|
||||||
|
// a reference count -- but for now, starting simple.
|
||||||
|
|
||||||
|
// Failures to create a tempDir are propagated as IOException and handled by transform
|
||||||
|
File tempDir = createTempDir();
|
||||||
|
try {
|
||||||
|
return ClassInjector.UsingInstrumentation.of(
|
||||||
|
tempDir,
|
||||||
|
ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,
|
||||||
|
AgentInstaller.getInstrumentation())
|
||||||
|
.injectRaw(classnameToBytes);
|
||||||
|
} finally {
|
||||||
|
// Delete fails silently
|
||||||
|
deleteTempDir(tempDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<?>> injectClassLoader(
|
||||||
|
final ClassLoader classLoader, final Map<String, byte[]> classnameToBytes) {
|
||||||
|
return new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes);
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureModuleCanReadHelperModules(final JavaModule target) {
|
private void ensureModuleCanReadHelperModules(final JavaModule target) {
|
||||||
if (JavaModule.isSupported() && target != JavaModule.UNSUPPORTED && target.isNamed()) {
|
if (JavaModule.isSupported() && target != JavaModule.UNSUPPORTED && target.isNamed()) {
|
||||||
for (final WeakReference<Object> helperModuleReference : helperModules) {
|
for (final WeakReference<Object> helperModuleReference : helperModules) {
|
||||||
|
@ -162,4 +191,18 @@ public class HelperInjector implements Transformer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final File createTempDir() throws IOException {
|
||||||
|
return Files.createTempDirectory("datadog-temp-jars").toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final void deleteTempDir(final File file) {
|
||||||
|
// Not using Files.delete for deleting the directory because failures
|
||||||
|
// create Exceptions which may prove expensive. Instead using the
|
||||||
|
// older File API which simply returns a boolean.
|
||||||
|
boolean deleted = file.delete();
|
||||||
|
if (!deleted) {
|
||||||
|
file.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,9 @@ public interface Instrumenter {
|
||||||
AgentBuilder.Identified.Extendable agentBuilder) {
|
AgentBuilder.Identified.Extendable agentBuilder) {
|
||||||
final String[] helperClassNames = helperClassNames();
|
final String[] helperClassNames = helperClassNames();
|
||||||
if (helperClassNames.length > 0) {
|
if (helperClassNames.length > 0) {
|
||||||
agentBuilder = agentBuilder.transform(new HelperInjector(helperClassNames));
|
agentBuilder =
|
||||||
|
agentBuilder.transform(
|
||||||
|
new HelperInjector(this.getClass().getSimpleName(), helperClassNames));
|
||||||
}
|
}
|
||||||
return agentBuilder;
|
return agentBuilder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,8 +322,10 @@ public class FieldBackedProvider implements InstrumentationContextProvider {
|
||||||
/** Get transformer that forces helper injection onto bootstrap classloader. */
|
/** Get transformer that forces helper injection onto bootstrap classloader. */
|
||||||
private AgentBuilder.Transformer bootstrapHelperInjector(
|
private AgentBuilder.Transformer bootstrapHelperInjector(
|
||||||
final Collection<DynamicType.Unloaded<?>> helpers) {
|
final Collection<DynamicType.Unloaded<?>> helpers) {
|
||||||
|
// TODO: Better to pass through the context of the Instrumenter
|
||||||
return new AgentBuilder.Transformer() {
|
return new AgentBuilder.Transformer() {
|
||||||
final HelperInjector injector = HelperInjector.forDynamicTypes(helpers);
|
final HelperInjector injector =
|
||||||
|
HelperInjector.forDynamicTypes(this.getClass().getSimpleName(), helpers);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DynamicType.Builder<?> transform(
|
public DynamicType.Builder<?> transform(
|
||||||
|
|
|
@ -102,7 +102,9 @@ public class MuzzleVersionScanPlugin {
|
||||||
// verify helper injector works
|
// verify helper injector works
|
||||||
final String[] helperClassNames = defaultInstrumenter.helperClassNames();
|
final String[] helperClassNames = defaultInstrumenter.helperClassNames();
|
||||||
if (helperClassNames.length > 0) {
|
if (helperClassNames.length > 0) {
|
||||||
new HelperInjector(createHelperMap(defaultInstrumenter))
|
new HelperInjector(
|
||||||
|
MuzzleVersionScanPlugin.class.getSimpleName(),
|
||||||
|
createHelperMap(defaultInstrumenter))
|
||||||
.transform(null, null, userClassLoader, null);
|
.transform(null, null, userClassLoader, null);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ class HelperInjectionTest extends DDSpecification {
|
||||||
def "helpers injected to non-delegating classloader"() {
|
def "helpers injected to non-delegating classloader"() {
|
||||||
setup:
|
setup:
|
||||||
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
||||||
HelperInjector injector = new HelperInjector(helperClassName)
|
HelperInjector injector = new HelperInjector("test", helperClassName)
|
||||||
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
|
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
|
||||||
|
|
||||||
when:
|
when:
|
||||||
|
@ -56,7 +56,7 @@ class HelperInjectionTest extends DDSpecification {
|
||||||
ByteBuddyAgent.install()
|
ByteBuddyAgent.install()
|
||||||
AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation())
|
AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation())
|
||||||
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
|
||||||
HelperInjector injector = new HelperInjector(helperClassName)
|
HelperInjector injector = new HelperInjector("test", helperClassName)
|
||||||
URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null)
|
URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
|
|
Loading…
Reference in New Issue