Skip to content

Commit

Permalink
GraalVM updates to extract the shared lib with native image instead o…
Browse files Browse the repository at this point in the history
…f package as resource (#816)

Co-authored-by: Michael Graeb <[email protected]>
  • Loading branch information
TingDaoK and graebm authored Sep 13, 2024
1 parent 4d34c94 commit 7f13df9
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 136 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ jobs:
python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')"
python builder.pyz build -p ${{ env.PACKAGE_NAME }} downstream
macos:
macos:
runs-on: macos-14 #latest
steps:
- name: Checkout Sources
Expand All @@ -204,7 +204,7 @@ jobs:
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
chmod a+x builder
./builder build -p ${{ env.PACKAGE_NAME }} --spec=downstream
python3 codebuild/macos_compatibility_check.py
python3 codebuild/macos_compatibility_check.py
macos-x64:
runs-on: macos-14-large #latest
Expand Down Expand Up @@ -274,7 +274,7 @@ jobs:
# check that docs can still build
check-docs:
runs-on: ubuntu-22.04 # latest
runs-on: ubuntu-22.04 # use same version as docs.yml
steps:
- uses: actions/checkout@v3
with:
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ on:

jobs:
update-docs-branch:
runs-on: ubuntu-20.04 # latest
runs-on: ubuntu-22.04 # use same version as ci.yml's check-docs
permissions:
contents: write # allow push
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true

- name: Update docs branch
run: |
./make-docs.sh
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ Platforms without FIPS compliance are also included in this jar, for compatibili
> [!WARNING]
> The classifier, and platforms with FIPS compliance are subject to change in the future.
## GraalVM support

Since version v0.29.20, GraalVM native image was supported. You can compile your application with AWS CRT in a GraalVM native image project without any additional configuration.

Since version v0.30.12, GraalVM support was updated. Instead of packaging the JNI shared lib with native image as resource, the corresponding shared lib will be written to the same directory as the native image.
In this way, it reduces the native image size around 30%, and avoids the extra loading time needed for extracting the JNI lib to the temporary path for load. No additional configuration needed.
**Note**: the JNI shared lib must be in the same directory as the GraalVM native image. If you move the native image, you must move this file too. It is `aws-crt-jni.dll` on Windows, `libaws-crt-jni.dylib` on macOS, and `libaws-crt-jni.so` on Unix.

## System Properties

Expand Down
1 change: 1 addition & 0 deletions android/crt/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ android {
main {
java.srcDir '../../src/main/java'
java.srcDir 'src/main/java'
java.exclude '**/GraalVMNativeFeature.java'
}
androidTest {
setRoot '../../src/test'
Expand Down
12 changes: 0 additions & 12 deletions javadoc.options

This file was deleted.

5 changes: 4 additions & 1 deletion make-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pushd $(dirname $0) > /dev/null
rm -rf docs/

# build
javadoc @javadoc.options
mvn javadoc:javadoc -Dmaven.javadoc.failOnWarnings=true

# copy to docs/
cp -r target/site/apidocs/ docs/

popd > /dev/null
28 changes: 26 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
<!-- cmake configure -->
<execution>
<id>cmake-configure</id>
<phase>generate-sources</phase>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
Expand Down Expand Up @@ -292,7 +292,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down Expand Up @@ -351,6 +351,13 @@
<version>1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>21.3.11</version><!-- 21.3.11 is the last version to support JDK8 -->
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
Expand Down Expand Up @@ -484,6 +491,23 @@
<forkCount>0</forkCount>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<windowtitle>AWS Common Runtime for Java/JVM</windowtitle>
<doctitle>AWS Common Runtime for Java/JVM</doctitle>
<header>AWS Common Runtime for Java/JVM</header>
<bottom>Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved.</bottom>
<show>public</show>
<sourcepath>src/main/java</sourcepath>
<notimestamp>true</notimestamp>
<quiet>true</quiet>
<doclint>all</doclint>
<excludePackageNames>software.amazon.awssdk.crt.internal</excludePackageNames>
</configuration>
</plugin>
</plugins>
</build>
</project>
159 changes: 59 additions & 100 deletions src/main/java/software/amazon/awssdk/crt/CRT.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
*/
package software.amazon.awssdk.crt;

import software.amazon.awssdk.crt.internal.ExtractLib;
import software.amazon.awssdk.crt.io.ClientBootstrap;
import software.amazon.awssdk.crt.io.EventLoopGroup;
import software.amazon.awssdk.crt.io.HostResolver;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
Expand All @@ -29,7 +28,7 @@ public final class CRT {
private static final String CRT_ARCH_OVERRIDE_SYSTEM_PROPERTY = "aws.crt.arch";
private static final String CRT_ARCH_OVERRIDE_ENVIRONMENT_VARIABLE = "AWS_CRT_ARCH";

private static final String CRT_LIB_NAME = "aws-crt-jni";
public static final String CRT_LIB_NAME = "aws-crt-jni";
public static final int AWS_CRT_SUCCESS = 0;
private static final CrtPlatform s_platform;

Expand All @@ -41,8 +40,15 @@ public final class CRT {
// If the lib is already present/loaded or is in java.library.path, just use it
System.loadLibrary(CRT_LIB_NAME);
} catch (UnsatisfiedLinkError e) {
// otherwise, load from the jar this class is in
loadLibraryFromJar();
String graalVMImageCode = System.getProperty("org.graalvm.nativeimage.imagecode");
if (graalVMImageCode != null && graalVMImageCode == "runtime") {
throw new CrtRuntimeException(
"Failed to load '" + System.mapLibraryName(CRT_LIB_NAME) +
"'. Make sure this file is in the same directory as the GraalVM native image. ");
} else {
// otherwise, load from the jar this class is in
loadLibraryFromJar();
}
}

// Initialize the CRT
Expand Down Expand Up @@ -246,112 +252,65 @@ private static List<String> runProcess(String[] cmdArray) throws IOException {
}

private static void extractAndLoadLibrary(String path) {
// Check java.io.tmpdir
String tmpdirPath;
File tmpdirFile;
try {
// Check java.io.tmpdir
String tmpdirPath;
File tmpdirFile;
try {
tmpdirFile = new File(path).getAbsoluteFile();
tmpdirPath = tmpdirFile.getAbsolutePath();
if (tmpdirFile.exists()) {
if (!tmpdirFile.isDirectory()) {
throw new IOException("not a directory: " + tmpdirPath);
}
} else {
tmpdirFile.mkdirs();
}

if (!tmpdirFile.canRead() || !tmpdirFile.canWrite()) {
throw new IOException("access denied: " + tmpdirPath);
}
} catch (Exception ex) {
String msg = "Invalid directory: " + path;
throw new IOException(msg, ex);
}

String libraryName = System.mapLibraryName(CRT_LIB_NAME);

// Prefix the lib we'll extract to disk
String tempSharedLibPrefix = "AWSCRT_";

File tempSharedLib = File.createTempFile(tempSharedLibPrefix, libraryName, tmpdirFile);
if (!tempSharedLib.setExecutable(true, true)) {
throw new CrtRuntimeException("Unable to make shared library executable by owner only");
}
if (!tempSharedLib.setWritable(true, true)) {
throw new CrtRuntimeException("Unable to make shared library writeable by owner only");
}
if (!tempSharedLib.setReadable(true, true)) {
throw new CrtRuntimeException("Unable to make shared library readable by owner only");
}

// The temp lib file should be deleted when we're done with it.
// Ask Java to try and delete it on exit. We call this immediately
// so that if anything goes wrong writing the file to disk, or
// loading it as a shared lib, it will still get cleaned up.
tempSharedLib.deleteOnExit();

// Unfortunately File.deleteOnExit() won't work on Windows, where
// files cannot be deleted while they're in use. On Windows, once
// our .dll is loaded, it can't be deleted by this process.
//
// The Windows-only solution to this problem is to scan on startup
// for old instances of the .dll and try to delete them. If another
// process is still using the .dll, the delete will fail, which is fine.
String os = getOSIdentifier();
if (os.equals("windows")) {
tryDeleteOldLibrariesFromTempDir(tmpdirFile, tempSharedLibPrefix, libraryName);
}

// open a stream to read the shared lib contents from this JAR
String libResourcePath = "/" + os + "/" + getArchIdentifier() + "/" + getCRuntime(os) + "/" + libraryName;
// Check whether there is a platform specific resource path to use
CrtPlatform platform = getPlatformImpl();
if (platform != null){
String platformLibResourcePath = platform.getResourcePath(getCRuntime(os), libraryName);
if (platformLibResourcePath != null){
libResourcePath = platformLibResourcePath;
tmpdirFile = new File(path).getAbsoluteFile();
tmpdirPath = tmpdirFile.getAbsolutePath();
if (tmpdirFile.exists()) {
if (!tmpdirFile.isDirectory()) {
throw new IOException("not a directory: " + tmpdirPath);
}
} else {
tmpdirFile.mkdirs();
}

try (InputStream in = CRT.class.getResourceAsStream(libResourcePath)) {
if (in == null) {
throw new IOException("Unable to open library in jar for AWS CRT: " + libResourcePath);
}

// Copy from jar stream to temp file
try (FileOutputStream out = new FileOutputStream(tempSharedLib)) {
int read;
byte [] bytes = new byte[1024];
while ((read = in.read(bytes)) != -1){
out.write(bytes, 0, read);
}
}
if (!tmpdirFile.canRead() || !tmpdirFile.canWrite()) {
throw new IOException("access denied: " + tmpdirPath);
}
} catch (IOException ex) {
CrtRuntimeException rex = new CrtRuntimeException("Invalid directory: " + path);
rex.initCause(ex);
throw rex;
}

if (!tempSharedLib.setWritable(false)) {
throw new CrtRuntimeException("Unable to make shared library read-only");
}
String libraryName = System.mapLibraryName(CRT_LIB_NAME);

// load the shared lib from the temp path
System.load(tempSharedLib.getAbsolutePath());
} catch (CrtRuntimeException crtex) {
System.err.println("Unable to initialize AWS CRT: " + crtex);
crtex.printStackTrace();
throw crtex;
} catch (UnknownPlatformException upe) {
System.err.println("Unable to determine platform for AWS CRT: " + upe);
upe.printStackTrace();
CrtRuntimeException rex = new CrtRuntimeException("Unable to determine platform for AWS CRT");
rex.initCause(upe);
throw rex;
} catch (Exception ex) {
System.err.println("Unable to unpack AWS CRT lib: " + ex);
// Prefix the lib we'll extract to disk
String tempSharedLibPrefix = "AWSCRT_";
File tempSharedLib = null;
try{
tempSharedLib = File.createTempFile(tempSharedLibPrefix, libraryName, tmpdirFile);
}
catch (IOException ex){
System.err.println("Unable to create temp file to extract AWS CRT library: " + ex);
ex.printStackTrace();
CrtRuntimeException rex = new CrtRuntimeException("Unable to unpack AWS CRT library");
CrtRuntimeException rex = new CrtRuntimeException("Unable to create temp file to extract AWS CRT library");
rex.initCause(ex);
throw rex;
}
// The temp lib file should be deleted when we're done with it.
// Ask Java to try and delete it on exit. We call this immediately
// so that if anything goes wrong writing the file to disk, or
// loading it as a shared lib, it will still get cleaned up.
tempSharedLib.deleteOnExit();

// Unfortunately File.deleteOnExit() won't work on Windows, where
// files cannot be deleted while they're in use. On Windows, once
// our .dll is loaded, it can't be deleted by this process.
//
// The Windows-only solution to this problem is to scan on startup
// for old instances of the .dll and try to delete them. If another
// process is still using the .dll, the delete will fail, which is fine.
String os = getOSIdentifier();
if (os.equals("windows")) {
tryDeleteOldLibrariesFromTempDir(tmpdirFile, tempSharedLibPrefix, libraryName);
}
ExtractLib.extractLibrary(tempSharedLib);
// load the shared lib from the temp path
System.load(tempSharedLib.getAbsolutePath());

}

private static void loadLibraryFromJar() {
Expand Down
Loading

0 comments on commit 7f13df9

Please sign in to comment.