-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathCRT.java
More file actions
566 lines (501 loc) · 23.1 KB
/
CRT.java
File metadata and controls
566 lines (501 loc) · 23.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
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.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Pattern;
/**
* This class is responsible for loading the aws-crt-jni shared lib for the
* current platform out of aws-crt-java.jar. One instance of this class has to
* be created somewhere to invoke the static initialization block which will
* load the shared lib
*/
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";
public static final String CRT_LIB_NAME = "aws-crt-jni";
public static final int AWS_CRT_SUCCESS = 0;
private static CrtPlatform s_platform;
private static final Throwable s_loadFailure;
static {
// The static initializer must never throw. If native load or init fails, capture
// the cause into s_loadFailure so callers (via checkLoaded()) can surface it as a
// catchable CrtRuntimeException, instead of triggering ExceptionInInitializerError
// and subsequent NoClassDefFoundError for every CRT type the customer touches.
Throwable loadFailure = null;
try {
// Scan for and invoke any platform specific initialization
s_platform = findPlatformImpl();
jvmInit();
try {
// If the lib is already present/loaded or is in java.library.path, just use it
System.loadLibrary(CRT_LIB_NAME);
} catch (UnsatisfiedLinkError e) {
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
int memoryTracingLevel = 0;
try {
memoryTracingLevel = Integer.parseInt(System.getProperty("aws.crt.memory.tracing"));
} catch (Exception ex) {
}
boolean debugWait = System.getProperty("aws.crt.debugwait") != null;
boolean strictShutdown = System.getProperty("aws.crt.strictshutdown") != null;
awsCrtInit(memoryTracingLevel, debugWait, strictShutdown);
Runtime.getRuntime().addShutdownHook(new Thread()
{
public void run()
{
CRT.releaseShutdownRef();
}
});
try {
Log.initLoggingFromSystemProperties();
} catch (IllegalArgumentException e) {
;
}
} catch (Throwable t) {
loadFailure = t;
}
s_loadFailure = loadFailure;
}
/**
* Throws a {@link CrtRuntimeException} if the CRT native library failed to load or initialize.
* Otherwise returns normally. Customers can call this directly to probe availability, or rely on
* {@link CrtResource} subclasses (e.g. {@code S3Client}, {@code EventLoopGroup}) to invoke it
* during construction.
*/
public static void checkLoaded() {
if (s_loadFailure != null) {
CrtRuntimeException ex = new CrtRuntimeException("Failed to load AWS CRT native library");
ex.initCause(s_loadFailure);
throw ex;
}
}
/**
* Exception thrown when we can't detect what platform we're running on and thus can't figure out
* the native library name/path to load.
*/
public static class UnknownPlatformException extends RuntimeException {
UnknownPlatformException(String message) {
super(message);
}
}
private static String normalize(String value) {
if (value == null) {
return "";
}
return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
}
/**
* @return a string describing the detected platform the CRT is executing on
*/
public static String getOSIdentifier() throws UnknownPlatformException {
CrtPlatform platform = getPlatformImpl();
String name = normalize(platform != null ? platform.getOSIdentifier() : System.getProperty("os.name"));
if (name.contains("windows")) {
return "windows";
} else if (name.contains("linux")) {
return "linux";
} else if (name.contains("freebsd")) {
return "freebsd";
} else if (name.contains("macosx")) {
return "osx";
} else if (name.contains("sun os") || name.contains("sunos") || name.contains("solaris")) {
return "solaris";
} else if (name.contains("android")){
return "android";
}
throw new UnknownPlatformException("AWS CRT: OS not supported: " + name);
}
/**
* @return a string describing the detected architecture the CRT is executing on
*/
public static String getArchIdentifier() throws UnknownPlatformException {
String systemPropertyOverride = System.getProperty(CRT_ARCH_OVERRIDE_SYSTEM_PROPERTY);
if (systemPropertyOverride != null && systemPropertyOverride.length() > 0) {
return systemPropertyOverride;
}
String environmentOverride = System.getenv(CRT_ARCH_OVERRIDE_ENVIRONMENT_VARIABLE);
if (environmentOverride != null && environmentOverride.length() > 0) {
return environmentOverride;
}
CrtPlatform platform = getPlatformImpl();
String arch = normalize(platform != null ? platform.getArchIdentifier() : System.getProperty("os.arch"));
if (arch.matches("^(x8664|amd64|ia32e|em64t|x64|x86_64)$")) {
return "x86_64";
} else if (arch.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
return "x86_32";
} else if (arch.startsWith("armeabi")) {
if (arch.contains("v7")) {
return "armv7";
} else {
return "armv6";
}
} else if (arch.startsWith("arm64") || arch.startsWith("aarch64")) {
return "armv8";
} else if (arch.equals("armv7l")) {
return "armv7";
} else if (arch.startsWith("arm")) {
return "armv6";
}
throw new UnknownPlatformException("AWS CRT: architecture not supported: " + arch);
}
private static final String NON_LINUX_RUNTIME_TAG = "cruntime";
private static final String MUSL_RUNTIME_TAG = "musl";
private static final String GLIBC_RUNTIME_TAG = "glibc";
public static String getCRuntime(String osIdentifier) {
if (!osIdentifier.equals("linux")) {
return NON_LINUX_RUNTIME_TAG;
}
// If system property is set, use that.
String systemPropertyOverride = System.getProperty("aws.crt.libc");
if (systemPropertyOverride != null) {
systemPropertyOverride = systemPropertyOverride.toLowerCase().trim();
if (!systemPropertyOverride.isEmpty()) {
return systemPropertyOverride;
}
}
// Be warned, the system might have both musl and glibc on it:
// https://github.com/awslabs/aws-crt-java/issues/659
// Next, check which one java is using.
// Run: ldd /path/to/java
// If musl, has a line like: libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f7732ae4000)
// If glibc, has a line like: libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f112c894000)
Pattern muslWord = Pattern.compile("\\bmusl\\b", Pattern.CASE_INSENSITIVE);
Pattern libcWord = Pattern.compile("\\blibc\\b", Pattern.CASE_INSENSITIVE);
String javaHome = System.getProperty("java.home");
if (javaHome != null) {
File javaExecutable = new File(new File(javaHome, "bin"), "java");
if (javaExecutable.exists()) {
try {
String[] lddJavaCmd = {"ldd", javaExecutable.toString()};
List<String> lddJavaOutput = runProcess(lddJavaCmd);
for (String line : lddJavaOutput) {
// check if the "libc" line mentions "musl"
if (libcWord.matcher(line).find()) {
if (muslWord.matcher(line).find()) {
return MUSL_RUNTIME_TAG;
} else {
return GLIBC_RUNTIME_TAG;
}
}
}
// uncertain, continue to next check
} catch (IOException ex) {
// uncertain, continue to next check
}
}
}
// Next, check whether ldd says it's using musl
// Run: ldd --version
// If musl, has a line like: musl libc (x86_64)
try {
String[] lddVersionCmd = {"ldd", "--version"};
List<String> lddVersionOutput = runProcess(lddVersionCmd);
for (String line : lddVersionOutput) {
// any mention of "musl" is sufficient
if (muslWord.matcher(line).find()) {
return MUSL_RUNTIME_TAG;
}
}
// uncertain, continue to next check
} catch (IOException io) {
// uncertain, continue to next check
}
// Assume it's glibc
return GLIBC_RUNTIME_TAG;
}
// Run process and return lines of output.
// Output is stdout and stderr merged together.
// The exit code is ignored.
// We do it this way because, on some Linux distros (Alpine),
// "ldd --version" reports exit code 1 and prints to stderr.
// But on most Linux distros it reports exit code 0 and prints to stdout.
private static List<String> runProcess(String[] cmdArray) throws IOException {
java.lang.Process proc = new ProcessBuilder(cmdArray)
.redirectErrorStream(true) // merge stderr into stdout
.start();
// confusingly, getInputStream() gets you stdout
BufferedReader outputReader = new BufferedReader(new
InputStreamReader(proc.getInputStream()));
String line;
List<String> output = new ArrayList<String>();
while ((line = outputReader.readLine()) != null) {
output.add(line);
}
return output;
}
private static void extractAndLoadLibrary(String path) {
// 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 (IOException ex) {
CrtRuntimeException rex = new CrtRuntimeException("Invalid directory: " + path);
rex.initCause(ex);
throw rex;
}
String libraryName = System.mapLibraryName(CRT_LIB_NAME);
// 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 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();
ExtractLib.extractLibrary(tempSharedLib);
// load the shared lib from the temp path
System.load(tempSharedLib.getAbsolutePath());
// Unfortunately, we can't rely solely on File.deleteOnExit() to clean things up, so we also try to clean up manually.
String os = getOSIdentifier();
if (os.equals("windows")) {
// File.deleteOnExit() and File.delete() 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.
tryDeleteOldLibrariesFromTempDir(tmpdirFile, tempSharedLibPrefix, libraryName);
} else {
// In some edge cases, File.deleteOnExit() might not run in cases like Lambda SnapStart or abnormal termination of the JVM.
// Therefore, we delete the library file immediately after it has been successfully loaded.
// This is safe to do on non-Windows platforms; everything will keep working in the current process.
tempSharedLib.delete();
}
}
private static void loadLibraryFromJar() {
// By default, just try java.io.tmpdir
List<String> pathsToTry = new LinkedList<>();
pathsToTry.add(System.getProperty("java.io.tmpdir"));
// If aws.crt.lib.dir is specified, try that first
String overrideLibDir = System.getProperty("aws.crt.lib.dir");
if (overrideLibDir != null) {
pathsToTry.add(0, overrideLibDir);
}
List<Exception> exceptions = new LinkedList<>();
for (String path : pathsToTry) {
try {
extractAndLoadLibrary(path);
return;
} catch (CrtRuntimeException ex) {
exceptions.add(ex);
}
}
// Aggregate the exceptions in order and throw a single failure exception
StringBuilder failureMessage = new StringBuilder();
exceptions.stream().map(Exception::toString).forEach(failureMessage::append);
throw new CrtRuntimeException(failureMessage.toString());
}
// Try to delete old CRT libraries that were extracted to the temp dir by previous runs.
private static void tryDeleteOldLibrariesFromTempDir(File tmpDir, String libNamePrefix, String libNameSuffix) {
try {
File[] oldLibs = tmpDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(libNamePrefix) && name.endsWith(libNameSuffix);
}
});
// Don't delete files that are too new.
// We don't want to delete another process's lib in the
// millisecond between the file being written to disk,
// and the file being loaded as a shared lib.
long aFewMomentsAgo = System.currentTimeMillis() - 10_000; // 10sec
for (File oldLib : oldLibs) {
try {
if (oldLib.lastModified() < aFewMomentsAgo) {
oldLib.delete();
}
} catch (Exception e) {}
}
} catch (Exception e) {}
}
private static CrtPlatform findPlatformImpl() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
String[] platforms = new String[] {
// Search for OS specific test impl first
String.format("software.amazon.awssdk.crt.test.%s.CrtPlatformImpl", getOSIdentifier()),
// Search for android test impl specifically because getOSIdentifier will return "linux" on android
"software.amazon.awssdk.crt.test.android.CrtPlatformImpl",
"software.amazon.awssdk.crt.android.CrtPlatformImpl",
// Fall back to crt
String.format("software.amazon.awssdk.crt.%s.CrtPlatformImpl", getOSIdentifier()), };
for (String platformImpl : platforms) {
try {
Class<?> platformClass = classLoader.loadClass(platformImpl);
CrtPlatform instance = (CrtPlatform) platformClass.getDeclaredConstructor().newInstance();
return instance;
} catch (ClassNotFoundException ex) {
// IGNORED
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException ex) {
throw new CrtRuntimeException(ex.toString());
}
}
return null;
}
public static CrtPlatform getPlatformImpl() {
return s_platform;
}
private static void jvmInit() {
CrtPlatform platform = getPlatformImpl();
if (platform != null) {
platform.jvmInit();
}
}
private static int shutdownRefCount = 1;
/**
* Public API that allows a user to indicate interest in controlling the CRT's time of shutdown. The
* shutdown process works via ref-counting, with a default starting count of 1 which is decremented by a
* JVM shutdown hook. Each external call to `acquireShutdownRef()` requires a corresponding call to
* `releaseShutdownRef()` when the caller is ready for the CRT to be shut down. Once all shutdown references
* have been released, the CRT will be shut down.
*
* If the ref count is not properly driven to zero (and thus leaving the CRT active), the JVM may crash
* if unmanaged native code in the CRT is still busy and attempts to call back into the JVM after the JVM cleans
* up JNI.
*/
public static void acquireShutdownRef() {
synchronized(CRT.class) {
if (shutdownRefCount <= 0) {
throw new CrtRuntimeException("Cannot acquire CRT shutdown when ref count is non-positive");
}
++shutdownRefCount;
}
}
/**
* Public API to release a shutdown reference that blocks CRT shutdown from proceeding. Must be called once, and
* only once, for each call to `acquireShutdownRef()`. Once all shutdown references have been released (including
* the initial reference that is managed by a JVM shutdown hook), the CRT will begin its shutdown process which
* permanently severs all native-JVM interactions.
*/
public static void releaseShutdownRef() {
boolean invoke_native_shutdown = false;
synchronized(CRT.class) {
if (shutdownRefCount <= 0) {
throw new CrtRuntimeException("Cannot release CRT shutdown when ref count is non-positive");
}
--shutdownRefCount;
if (shutdownRefCount == 0) {
invoke_native_shutdown = true;
}
}
if (invoke_native_shutdown) {
onJvmShutdown();
ClientBootstrap.closeStaticDefault();
EventLoopGroup.closeStaticDefault();
HostResolver.closeStaticDefault();
}
}
// Called internally when bootstrapping the CRT, allows native code to do any
// static initialization it needs
private static native void awsCrtInit(int memoryTracingLevel, boolean debugWait, boolean strictShutdown)
throws CrtRuntimeException;
/**
* Returns the last error on the current thread.
*
* @return Last error code recorded in this thread
*/
public static native int awsLastError();
/**
* Given an integer error code from an internal operation, get a corresponding description for it.
*
* @param errorCode An error code returned from an exception or other native
* function call
* @return A user-friendly description of the error
*/
public static native String awsErrorString(int errorCode);
/**
* Given an integer error code from an internal operation, get a corresponding string identifier for it.
*
* @param errorCode An error code returned from an exception or other native
* function call
* @return A string identifier for the error
*/
public static native String awsErrorName(int errorCode);
/**
* Given an error code, get a boolean to check if an error is transient or not.
*
* Transient errors are defined as IO level errors where we are unable to read an HTTP response.
* This can occur due to connect timeouts, read timeouts, or the server closing the connection without
* sending a response. This method helps identify CRT error codes that are not generic or widely adopted.
*
* This is not the complete logic to identify transient or retryable errors, this includes only IO level
* errors that are transient.
*
* @param errorCode An error code returned from an exception or other native function call
* @return A boolean for if the error is transient or not
*/
public static native boolean awsIsTransientError(int errorCode);
/**
* @return The number of bytes allocated in native resources. If
* aws.crt.memory.tracing is 1 or 2, this will be a non-zero value.
* Otherwise, no tracing will be done, and the value will always be 0
*/
public static long nativeMemory() {
return awsNativeMemory();
}
/**
* Dump info to logs about all memory currently allocated by native resources.
* The following system properties must be set to see a dump:
* aws.crt.memory.tracing must be 1 or 2
* aws.crt.log.level must be "Trace"
*/
public static native void dumpNativeMemory();
private static native long awsNativeMemory();
static void testJniException(boolean throwException) {
if (throwException) {
throw new RuntimeException("Testing");
}
}
public static void checkJniExceptionContract(boolean clearException) {
nativeCheckJniExceptionContract(clearException);
}
public static native boolean isFIPS();
private static native void nativeCheckJniExceptionContract(boolean clearException);
private static native void onJvmShutdown();
};