Skip to content

Commit 71c765d

Browse files
dougqhbric3
andauthored
Introducing NativeLoader (#9625)
* Introducing NativeLoader Introducing NativeLoader API intended to standardize how native libraries are loaded in dd-trace-java NativeLoader allows for using different file layout and file resolution strategies * spotless * Eliminate var-args warning Switched Objects.hash -> Arrays.hashCode * Remove unused import * Added preloaded test * Javadoc and misc clean-up * Removed errant comment * Tweaking Javadoc * More tests & javadoc Enabling the NativeLoader tests - using dummy lib files, but enough to test resolution process and linkage failures More Javadoc to help explain the various pluggable bits of the API * Solidifying exception handling Changed NativeLoader to raise LibraryLoadException rather than returning null when it encounters an unsupported OS or architecture Allowing LibraryResolver and PathLocator to both raise Exceptions Creating PathLocatorHelper to help with exception handling in LibraryResolver-s when multiple locator requests are being performed * CapturingPathResolver -> CapturingPathLocator Clean-up to account for prior name change of PathResolver to PathLocator * nativeloader -> native-loader * Adding FunctionalInterface * Removing public from function * Forbidden API fix * Overloading withPreloaded to take a Set - addressing review comments - overloading withPreloaded to have a version that takes a Set<String> * Hooking into arch detection code relocated to :components:environment Also added javadoc to PlatformSpec * Code coverage - PathUtils * spotless * More PathUtils coverage * PathLocatorHelper coverage * Coverage PlatformSpec * Adding ability to record multiple locate requests to CapturingPathLocator * Coverage of LibraryResolvers - reworked CapturingPathLocator to capture all requested paths - updated tests to cover all fallback paths - added external OS (without arch) fallback * Coverage PathLocators Coverage of anonymous inner class that adds preloaded support * Coverage LibDirBasedPathLocator Covering multi-dir case * PathLocators coverage More coverage of LibDirBasedPathLocator - including component support * Fixed bug with subResource loading in ClassLoaderResourcePathLocator More coverage * More coverage Covering DefaultPlatformSpec - now renamed IntrospectPlatformSpec * NativeLoader.Builder coverage * spotless * ClassLoaderBasedPathLocator coverage * LibFile coverage Aded more check to the assert helpers to improve LibFile coverage * Coverage NativeLoader.Builder - added tempDir tests * Testing tempDir functionality Fixed problem with tempDir support when tempDir doesn't already exist * Coverage - Testing PlatformSpec override variations of resolveDynamic * Temp file coverage Creating temp dirs with varying permissions to trigger problem cases Also added simulated case for being unable to immediately clean-up the temp file * spotless * Update components/native-loader/src/main/java/datadog/nativeloader/FlatDirLibraryResolver.java Co-authored-by: Brice Dutheil <[email protected]> * Addressing review comments - lowering visibility on a few classes * Addressing review comments - explicit equals call in test * Addressing review comments - be more explicit about equivalence tests * fixing merge conflict * spotless * Addressing review comments - LibraryResolver test clean up Added testFailOnExceptions helper to CapturingPathLocator Updated nested & flat resolvers tests to use new helper method Renamed test classes to match their corresponding classes * Addressing review comments - removing redundant final on methods * Addressing review comments - removing final on methods * spotless * Addressing review comments - check a not preloaded case for Builder * spotless * Adding explanatory comment for review question * Tweaking assertions for readability * Improving path concatenation handling Added a 2-arg version of PathUtils.concatPath Using new concatPath version in LibDirBasedPathLocator Added some header doc to both versions of concatPath * Removed test jar file Switched to creating test jar from test-data directory Added various utility functions to make tests easier to read --------- Co-authored-by: Brice Dutheil <[email protected]>
1 parent dc8540a commit 71c765d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2460
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
`java-library`
3+
}
4+
5+
apply(from = "$rootDir/gradle/java.gradle")
6+
7+
dependencies {
8+
implementation(project(":components:environment"))
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package datadog.nativeloader;
2+
3+
import java.net.URL;
4+
import java.util.Objects;
5+
6+
/** ClassLoaderResourcePathLocator locates library paths inside a {@link ClassLoader} */
7+
final class ClassLoaderResourcePathLocator implements PathLocator {
8+
private final ClassLoader classLoader;
9+
private final String baseResource;
10+
11+
public ClassLoaderResourcePathLocator(final ClassLoader classLoader, final String baseResource) {
12+
this.classLoader = classLoader;
13+
this.baseResource = baseResource;
14+
}
15+
16+
@Override
17+
public URL locate(String component, String path) {
18+
return this.classLoader.getResource(PathUtils.concatPath(component, this.baseResource, path));
19+
}
20+
21+
@Override
22+
public int hashCode() {
23+
return Objects.hash(this.classLoader, this.baseResource);
24+
}
25+
26+
@Override
27+
public boolean equals(Object obj) {
28+
if (!(obj instanceof ClassLoaderResourcePathLocator)) return false;
29+
30+
ClassLoaderResourcePathLocator that = (ClassLoaderResourcePathLocator) obj;
31+
return this.classLoader.equals(that.classLoader)
32+
&& Objects.equals(this.baseResource, that.baseResource);
33+
}
34+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package datadog.nativeloader;
2+
3+
import java.net.URL;
4+
5+
/**
6+
* FlatDirLibraryResolver - uses flat directories to provide more specific libraries to load <code>
7+
* {os}-{arch}-{libc/musl}</code>
8+
*/
9+
public final class FlatDirLibraryResolver implements LibraryResolver {
10+
public static final FlatDirLibraryResolver INSTANCE = new FlatDirLibraryResolver();
11+
12+
private FlatDirLibraryResolver() {}
13+
14+
@Override
15+
public final URL resolve(
16+
PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName)
17+
throws Exception {
18+
PathLocatorHelper pathLocatorHelper = new PathLocatorHelper(libName, pathLocator);
19+
20+
String libFileName = PathUtils.libFileName(platformSpec, libName);
21+
22+
String osPath = PathUtils.osPartOf(platformSpec);
23+
String archPath = PathUtils.archPartOf(platformSpec);
24+
String libcPath = PathUtils.libcPartOf(platformSpec);
25+
26+
URL url;
27+
String regularPath = osPath + "-" + archPath;
28+
29+
if (libcPath != null) {
30+
String specializedPath = regularPath + "-" + libcPath;
31+
url = pathLocatorHelper.locate(component, specializedPath + "/" + libFileName);
32+
if (url != null) return url;
33+
}
34+
35+
url = pathLocatorHelper.locate(component, regularPath + "/" + libFileName);
36+
if (url != null) return url;
37+
38+
url = pathLocatorHelper.locate(component, osPath + "/" + libFileName);
39+
if (url != null) return url;
40+
41+
// fallback to searching at top-level, mostly concession to good out-of-box behavior
42+
// with java.library.path
43+
url = pathLocatorHelper.locate(component, libFileName);
44+
if (url != null) return url;
45+
46+
if (component != null) {
47+
url = pathLocatorHelper.locate(null, libFileName);
48+
if (url != null) return url;
49+
}
50+
51+
pathLocatorHelper.tryThrow();
52+
53+
return null;
54+
}
55+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package datadog.nativeloader;
2+
3+
import datadog.environment.OperatingSystem;
4+
import datadog.environment.OperatingSystem.Architecture;
5+
6+
/*
7+
* Default PlatformSpec used in dd-trace-java -- wraps detection code in component:environment
8+
*/
9+
final class IntrospectPlatformSpec extends PlatformSpec {
10+
static final PlatformSpec INSTANCE = new IntrospectPlatformSpec();
11+
12+
@Override
13+
public boolean isLinux() {
14+
return OperatingSystem.isLinux();
15+
}
16+
17+
@Override
18+
public boolean isMac() {
19+
return OperatingSystem.isMacOs();
20+
}
21+
22+
@Override
23+
public boolean isWindows() {
24+
return OperatingSystem.isWindows();
25+
}
26+
27+
@Override
28+
public boolean isMusl() {
29+
return OperatingSystem.isMusl();
30+
}
31+
32+
@Override
33+
public boolean isAarch64() {
34+
return isArch(Architecture.ARM64);
35+
}
36+
37+
@Override
38+
public boolean isArm32() {
39+
return isArch(Architecture.ARM);
40+
}
41+
42+
@Override
43+
public boolean isX86_32() {
44+
return isArch(Architecture.X86);
45+
}
46+
47+
@Override
48+
public boolean isX86_64() {
49+
return isArch(Architecture.X64);
50+
}
51+
52+
static final boolean isArch(OperatingSystem.Architecture arch) {
53+
return (OperatingSystem.architecture() == arch);
54+
}
55+
56+
@Override
57+
public int hashCode() {
58+
return IntrospectPlatformSpec.class.hashCode();
59+
}
60+
61+
@Override
62+
public boolean equals(Object obj) {
63+
return (obj instanceof IntrospectPlatformSpec);
64+
}
65+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package datadog.nativeloader;
2+
3+
import java.io.File;
4+
import java.net.MalformedURLException;
5+
import java.net.URL;
6+
import java.util.Arrays;
7+
8+
/** LibDirBasedPathLocator locates libraries inside a list of library directories */
9+
final class LibDirBasedPathLocator implements PathLocator {
10+
private final File[] libDirs;
11+
12+
public LibDirBasedPathLocator(File... libDirs) {
13+
this.libDirs = libDirs;
14+
}
15+
16+
@Override
17+
public URL locate(String component, String path) {
18+
String fullPath = PathUtils.concatPath(component, path);
19+
20+
for (File libDir : this.libDirs) {
21+
File libFile = new File(libDir, fullPath);
22+
if (libFile.exists()) return toUrl(libFile);
23+
}
24+
25+
return null;
26+
}
27+
28+
@SuppressWarnings("deprecation")
29+
private static final URL toUrl(File file) {
30+
try {
31+
return file.toURL();
32+
} catch (MalformedURLException e) {
33+
return null;
34+
}
35+
}
36+
37+
@Override
38+
public int hashCode() {
39+
return Arrays.hashCode(this.libDirs);
40+
}
41+
42+
@Override
43+
public boolean equals(Object obj) {
44+
if (!(obj instanceof LibDirBasedPathLocator)) return false;
45+
46+
LibDirBasedPathLocator that = (LibDirBasedPathLocator) obj;
47+
return Arrays.equals(this.libDirs, that.libDirs);
48+
}
49+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package datadog.nativeloader;
2+
3+
import java.io.File;
4+
import java.nio.file.Path;
5+
6+
/**
7+
* Represents a resolved library
8+
*
9+
* <ul>
10+
* <li>library may be preloaded - with no backing file
11+
* <li>regular file - that doesn't require clean-up
12+
* <li>temporary file - copying from another source - that does require clean-up
13+
* </ul>
14+
*/
15+
public final class LibFile implements AutoCloseable {
16+
static final boolean NO_CLEAN_UP = false;
17+
static final boolean CLEAN_UP = true;
18+
19+
static final LibFile preloaded(String libName) {
20+
return new LibFile(libName, null, NO_CLEAN_UP);
21+
}
22+
23+
static final LibFile fromFile(String libName, File file) {
24+
return new LibFile(libName, file, NO_CLEAN_UP);
25+
}
26+
27+
static final LibFile fromTempFile(String libName, File file) {
28+
return new LibFile(libName, file, CLEAN_UP);
29+
}
30+
31+
final String libName;
32+
33+
final File file;
34+
final boolean needsCleanup;
35+
36+
LibFile(String libName, File file, boolean needsCleanup) {
37+
this.libName = libName;
38+
39+
this.file = file;
40+
this.needsCleanup = needsCleanup;
41+
}
42+
43+
/** Indicates if this library was "preloaded" */
44+
public boolean isPreloaded() {
45+
return (this.file == null);
46+
}
47+
48+
/** Loads the underlying library into the JVM */
49+
public void load() throws LibraryLoadException {
50+
if (this.isPreloaded()) return;
51+
52+
try {
53+
Runtime.getRuntime().load(this.getAbsolutePath());
54+
} catch (Throwable t) {
55+
throw new LibraryLoadException(this.libName, t);
56+
}
57+
}
58+
59+
/** Provides a File to the library -- returns null for pre-loaded libraries */
60+
public final File toFile() {
61+
return this.file;
62+
}
63+
64+
/** Provides a Path to the library -- return null for pre-loaded libraries */
65+
public final Path toPath() {
66+
return this.file == null ? null : this.file.toPath();
67+
}
68+
69+
/** Provides the an absolute path to the library -- returns null for pre-loaded libraries */
70+
public final String getAbsolutePath() {
71+
return this.file == null ? null : this.file.getAbsolutePath();
72+
}
73+
74+
/** Schedules clean-up of underlying file -- if the file is a temp file */
75+
@Override
76+
public void close() {
77+
if (this.needsCleanup) {
78+
NativeLoader.delete(this.file);
79+
}
80+
}
81+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package datadog.nativeloader;
2+
3+
/** Exception raised when NativeLoader fails to resolve or load a library */
4+
public class LibraryLoadException extends Exception {
5+
static final String UNSUPPORTED_OS = "Unsupported OS";
6+
static final String UNSUPPORTED_ARCH = "Unsupported arch";
7+
8+
private static final long serialVersionUID = 1L;
9+
10+
public LibraryLoadException(String libName) {
11+
super(message(libName));
12+
}
13+
14+
public LibraryLoadException(String libName, Throwable cause) {
15+
this(message(libName), cause.getMessage(), cause);
16+
}
17+
18+
public LibraryLoadException(String libName, String message) {
19+
super(message(libName) + " - " + message);
20+
}
21+
22+
public LibraryLoadException(String libName, String message, Throwable cause) {
23+
super(message(libName) + " - " + message, cause);
24+
}
25+
26+
static final String message(String libName) {
27+
return "Unable to resolve library " + libName;
28+
}
29+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package datadog.nativeloader;
2+
3+
import java.net.URL;
4+
5+
/**
6+
* LibraryResolver encapsulates a library resolution strategy
7+
*
8+
* <p>The LibraryResolver should use the provided {@link PathLocator} to locate the desired
9+
* resources. The LibraryResolver may try multiple locations to find the best possible library to
10+
* use.
11+
*/
12+
@FunctionalInterface
13+
public interface LibraryResolver {
14+
default boolean isPreloaded(PlatformSpec platform, String libName) {
15+
return false;
16+
}
17+
18+
URL resolve(PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName)
19+
throws Exception;
20+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package datadog.nativeloader;
2+
3+
import java.net.URL;
4+
import java.util.Arrays;
5+
import java.util.HashSet;
6+
import java.util.Set;
7+
8+
public final class LibraryResolvers {
9+
private LibraryResolvers() {}
10+
11+
public static final LibraryResolver defaultLibraryResolver() {
12+
return flatDirs();
13+
}
14+
15+
public static final LibraryResolver withPreloaded(
16+
LibraryResolver baseResolver, String... preloadedLibNames) {
17+
return withPreloaded(baseResolver, new HashSet<>(Arrays.asList(preloadedLibNames)));
18+
}
19+
20+
public static final LibraryResolver withPreloaded(
21+
LibraryResolver baseResolver, Set<String> preloadedLibNames) {
22+
return new LibraryResolver() {
23+
@Override
24+
public boolean isPreloaded(PlatformSpec platform, String libName) {
25+
return preloadedLibNames.contains(libName);
26+
}
27+
28+
@Override
29+
public URL resolve(
30+
PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName)
31+
throws Exception {
32+
return baseResolver.resolve(pathLocator, component, platformSpec, libName);
33+
}
34+
};
35+
}
36+
37+
public static final LibraryResolver flatDirs() {
38+
return FlatDirLibraryResolver.INSTANCE;
39+
}
40+
41+
public static final LibraryResolver nestedDirs() {
42+
return NestedDirLibraryResolver.INSTANCE;
43+
}
44+
}

0 commit comments

Comments
 (0)