-
Notifications
You must be signed in to change notification settings - Fork 336
Avoid temporary Jar file collisions #1285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3417fd8
c50899f
d2ecce4
4ad51dd
7a183e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.lang.ref.WeakReference; | ||
| import java.nio.file.Files; | ||
| import java.security.SecureClassLoader; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
|
|
@@ -33,6 +34,8 @@ public class HelperInjector implements Transformer { | |
| private static final ClassLoader BOOTSTRAP_CLASSLOADER_PLACEHOLDER = | ||
| new SecureClassLoader(null) {}; | ||
|
|
||
| private final String requestingName; | ||
|
|
||
| private final Set<String> helperClassNames; | ||
| 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 | ||
| * 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)); | ||
| } | ||
|
|
||
| public HelperInjector(final Map<String, byte[]> helperMap) { | ||
| public HelperInjector(final String requestingName, final Map<String, byte[]> helperMap) { | ||
| this.requestingName = requestingName; | ||
|
|
||
| helperClassNames = helperMap.keySet(); | ||
| 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()); | ||
| for (final DynamicType.Unloaded<?> helper : helpers) { | ||
| bytes.put(helper.getTypeDescription().getName(), helper.getBytes()); | ||
| } | ||
| return new HelperInjector(bytes); | ||
| return new HelperInjector(requestingName, bytes); | ||
| } | ||
|
|
||
| private Map<String, byte[]> getHelperMap() throws IOException { | ||
|
|
@@ -101,14 +109,9 @@ public DynamicType.Builder<?> transform( | |
| final Map<String, byte[]> classnameToBytes = getHelperMap(); | ||
| final Map<String, Class<?>> classes; | ||
| if (classLoader == BOOTSTRAP_CLASSLOADER_PLACEHOLDER) { | ||
| classes = | ||
|
dougqh marked this conversation as resolved.
|
||
| ClassInjector.UsingInstrumentation.of( | ||
| new File(System.getProperty("java.io.tmpdir")), | ||
| ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, | ||
| AgentInstaller.getInstrumentation()) | ||
| .injectRaw(classnameToBytes); | ||
| classes = injectBootstrapClassLoader(classnameToBytes); | ||
| } else { | ||
| classes = new ClassInjector.UsingReflection(classLoader).injectRaw(classnameToBytes); | ||
| classes = injectClassLoader(classLoader, classnameToBytes); | ||
| } | ||
|
|
||
| // All datadog helper classes are in the unnamed module | ||
|
|
@@ -125,8 +128,9 @@ public DynamicType.Builder<?> transform( | |
| : classLoader.getClass().getName(); | ||
|
|
||
| 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, | ||
| requestingName, | ||
| classLoader, | ||
| classLoaderType, | ||
| e); | ||
|
|
@@ -141,6 +145,31 @@ public DynamicType.Builder<?> transform( | |
| 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( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a further level of defense, we could do couple things here. The ClassInjector internally constructs a RandomString which in turn uses a Random. Java seeds Random objects not just with time, but also with another value that is changed for each Random object created. If wanted to provide a stronger guarantee, we could reuse the UsingInstrumentation object from injection to the next which would also reuse the underlying RandomString and Random objects. However, before doing that, we need to double check that all the classes involved are thread-safe. |
||
| 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) { | ||
| if (JavaModule.isSupported() && target != JavaModule.UNSUPPORTED && target.isNamed()) { | ||
| for (final WeakReference<Object> helperModuleReference : helperModules) { | ||
|
|
@@ -162,4 +191,18 @@ private void ensureModuleCanReadHelperModules(final JavaModule target) { | |
| } | ||
| } | ||
| } | ||
|
|
||
| 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(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field was added to improved our ability to debug problems when failures occur.