Merge pull request #1285 from DataDog/dougqh/jar-file-collisions

Avoid temporary Jar file collisions
This commit is contained in:
Douglas Q Hawkins 2020-03-06 15:14:25 -05:00 committed by GitHub
commit 3ff5b99cd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 17 deletions

View File

@ -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();
}
}
} }

View File

@ -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;
} }

View File

@ -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(

View File

@ -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) {

View File

@ -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: