-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathCrtResource.java
More file actions
532 lines (455 loc) · 18.7 KB
/
CrtResource.java
File metadata and controls
532 lines (455 loc) · 18.7 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
/**
* 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.io.ClientBootstrap;
import software.amazon.awssdk.crt.io.EventLoopGroup;
import software.amazon.awssdk.crt.io.HostResolver;
import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.HashMap;
import java.util.Map;
/**
* This wraps a native pointer and/or one or more references to an AWS Common Runtime resource. It also ensures
* that the first time a resource is referenced, the CRT will be loaded and bound.
*/
public abstract class CrtResource implements AutoCloseable {
private static final String NATIVE_DEBUG_PROPERTY_NAME = "aws.crt.debugnative";
private static final int DEBUG_CLEANUP_WAIT_TIME_IN_SECONDS = 60;
private static final long NULL = 0;
private static final Log.LogLevel ResourceLogLevel = Log.LogLevel.Debug;
/**
* Debug/diagnostic data about a CrtResource object
*/
public class ResourceInstance {
public long nativeHandle;
public final String canonicalName;
private Throwable instantiation;
private CrtResource wrapper;
public ResourceInstance(CrtResource wrapper, String name) {
canonicalName = name;
this.wrapper = wrapper;
if (debugNativeObjects) {
try {
throw new RuntimeException();
} catch (RuntimeException ex) {
instantiation = ex;
}
}
}
public String location() {
String str = "";
if (debugNativeObjects) {
StackTraceElement[] stack = instantiation.getStackTrace();
// skip ctor and acquireNativeHandle()
for (int frameIdx = 2; frameIdx < stack.length; ++frameIdx) {
StackTraceElement frame = stack[frameIdx];
str += frame.toString() + "\n";
}
}
return str;
}
@Override
public String toString() {
String str = canonicalName + " allocated at:\n";
str += location();
return str;
}
public CrtResource getWrapper() { return wrapper; }
public void setNativeHandle(long handle) { nativeHandle = handle; }
}
private static final HashMap<Long, ResourceInstance> CRT_RESOURCES = new HashMap<>();
/*
* Primarily intended for testing only. Tracks the number of non-closed resources and signals
* whenever a zero count is reached.
*/
private static boolean debugNativeObjects = System.getProperty(NATIVE_DEBUG_PROPERTY_NAME) != null;
private static int resourceCount = 0;
private static final Lock lock = new ReentrantLock();
private static final Condition emptyResources = lock.newCondition();
private static final AtomicLong nextId = new AtomicLong(0);
private final ArrayList<CrtResource> referencedResources = new ArrayList<>();
private long nativeHandle;
private AtomicInteger refCount = new AtomicInteger(1);
private long id = nextId.getAndAdd(1);
private Instant creationTime = Instant.now();
private String description;
static {
/* This will cause the JNI lib to be loaded the first time a CRT is created */
new CRT();
}
/**
* Default constructor
*/
public CrtResource() {
// If the native lib failed to load, surface a single, catchable CrtRuntimeException
// before any subclass touches a native method (which would throw UnsatisfiedLinkError).
CRT.checkLoaded();
if (debugNativeObjects) {
String canonicalName = this.getClass().getCanonicalName();
synchronized(CrtResource.class) {
CRT_RESOURCES.put(id, new ResourceInstance(this, canonicalName));
}
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource, String.format("CrtResource of class %s(%d) created", this.getClass().getCanonicalName(), id));
}
}
/**
* Marks a resource as referenced by this resource.
* @param resource The resource to add a reference to
*/
public void addReferenceTo(CrtResource resource) {
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) is adding a reference to %s(%d)",
this.getClass().getCanonicalName(), id, resource.getClass().getCanonicalName(), resource.id));
}
resource.addRef();
synchronized(this) {
referencedResources.add(resource);
}
}
/**
* Removes a reference from this resource to another.
* @param resource The resource to remove a reference to
*/
public void removeReferenceTo(CrtResource resource) {
boolean removed = false;
synchronized(this) {
removed = referencedResources.remove(resource);
}
if (debugNativeObjects) {
if (removed) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) is removing a reference to %s(%d)",
this.getClass().getCanonicalName(), id, resource.getClass().getCanonicalName(), resource.id));
} else {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) erroneously tried to remove a reference to %s(%d) that it was not referencing",
this.getClass().getCanonicalName(), id, resource.getClass().getCanonicalName(), resource.id));
}
}
if (!removed) {
return;
}
resource.decRef(this);
}
/**
* Swaps a reference from one resource to another
* @param oldReference resource to stop referencing
* @param newReference resource to start referencing
*/
protected void swapReferenceTo(CrtResource oldReference, CrtResource newReference) {
if (oldReference != newReference) {
if (newReference != null) {
addReferenceTo(newReference);
}
if (oldReference != null) {
removeReferenceTo(oldReference);
}
}
}
/**
* Takes ownership of a native object where the native pointer is tracked as a long.
* @param handle pointer to the native object being acquired
*/
protected void acquireNativeHandle(long handle) {
if (!isNull()) {
throw new IllegalStateException("Can't acquire >1 Native Pointer");
}
String canonicalName = this.getClass().getCanonicalName();
if (handle == NULL) {
throw new IllegalStateException("Can't acquire NULL Pointer: " + canonicalName);
}
if (debugNativeObjects) {
synchronized(CrtResource.class) {
ResourceInstance instance = CRT_RESOURCES.get(id);
if (instance != null) {
instance.setNativeHandle(handle);
}
}
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource, String.format("acquireNativeHandle - %s(%d) acquired native pointer %d", canonicalName, id, handle));
}
nativeHandle = handle;
incrementNativeObjectCount();
}
/**
* Begins the cleanup process associated with this native object and performs various debug-level bookkeeping operations.
*/
private void release() {
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("Releasing class %s(%d)", this.getClass().getCanonicalName(), id));
synchronized(CrtResource.class) {
CRT_RESOURCES.remove(id);
}
}
releaseNativeHandle();
if (nativeHandle != 0) {
decrementNativeObjectCount();
nativeHandle = 0;
}
}
/**
* returns the native handle associated with this CRTResource.
* @return native address
*/
public long getNativeHandle() {
return nativeHandle;
}
/**
* Increments the reference count to this resource.
*/
public void addRef() {
int updatedRefs = refCount.incrementAndGet();
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) is adding a reference. refCount is now %d",
this.getClass().getCanonicalName(), id, updatedRefs));
}
}
/**
* Increments the reference count to this resource with a description.
* @param desc Descrption string of why the reference is being incremented.
*/
public void addRef(String desc) {
int updatedRefs = refCount.incrementAndGet();
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) is adding a reference for: (%s). RefCount is now %d",
this.getClass().getCanonicalName(), id, desc, updatedRefs));
}
}
/**
* Required override method that must begin the release process of the acquired native handle
*/
protected abstract void releaseNativeHandle();
/**
* Override that determines whether a resource releases its dependencies at the same time the native handle is released or if it waits.
* Resources with asynchronous shutdown processes should override this with false, and establish a callback from native code that
* invokes releaseReferences() when the asynchronous shutdown process has completed. See HttpClientConnectionManager for an example.
* @return true if this resource releases synchronously, false if this resource performs async shutdown
*/
protected abstract boolean canReleaseReferencesImmediately();
/**
* Checks if this resource's native handle is NULL. For always-null resources this is always true. For all other
* resources it means it has already been cleaned up or was not properly constructed.
* @return true if no native resource is bound, false otherwise
*/
public boolean isNull() {
return (nativeHandle == NULL);
}
/*
* An ugly and unfortunate necessity. The CRTResource currently entangles two loosely-coupled concepts:
* (1) management of a native resource
* (2) referencing of other resources and the resulting implied cleanup process
*
* Some classes don't represent an actual native resource. Instead, they just want to use
* the reference and cleanup framework. See AwsIotMqttConnectionBuilder.java for example.
*
*/
@Override
public void close() {
decRef("close() called");
}
public void close(String desc) {
decRef(desc);
}
/**
* Decrements the reference count to this resource. If zero is reached, begins (and possibly completes) the resource's
* cleanup process.
* @param decRefInstigator Instigating CrtResource
*/
public void decRef(CrtResource decRefInstigator) {
int remainingRefs = refCount.decrementAndGet();
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource, String.format(
"DecRef of %s(%d) called by %s(%d). %d remaining refs", this.getClass().getCanonicalName(), id,
decRefInstigator.getClass().getCanonicalName(), decRefInstigator.id, remainingRefs));
}
if (remainingRefs != 0) {
return;
}
release();
if (canReleaseReferencesImmediately()) {
releaseReferences();
}
}
/**
* Decrements the reference count to this resource with a description.
* @param desc Descrption string of why the reference is being decremented.
*/
public void decRef(String desc) {
int remainingRefs = refCount.decrementAndGet();
if (debugNativeObjects) {
if (desc != null) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("DecRef on %s(%d) for: (%s). %d remaining refs",
this.getClass().getCanonicalName(), id, desc, remainingRefs));
} else {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("Defref on %s(%d). %d remaining refs",
this.getClass().getCanonicalName(), id, remainingRefs));
}
}
if (remainingRefs != 0) {
return;
}
release();
if (canReleaseReferencesImmediately()) {
releaseReferences();
}
}
/**
* Decrements the reference count to this resource.
*/
public void decRef() {
decRef((String)null);
}
/**
* Decrements the ref counts for all resources referenced by this resource. Most resources will have this called
* from their close() function, but resources with asynchronous shutdown processes must have it called from a
* shutdown completion callback.
*/
protected void releaseReferences() {
if (debugNativeObjects) {
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource,
String.format("%s(%d) closing all referenced objects",
this.getClass().getCanonicalName(), id));
}
synchronized(this) {
for (CrtResource resource : referencedResources) {
resource.decRef(this);
}
referencedResources.clear();
}
}
/**
* Sets a custom logging description for this resource
* @param description custom resource description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Gets a debug/diagnostic string describing this resource and its reference state
* @return resource diagnostic string
*/
public String getResourceLogDescription() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("[Id %d, Class %s, Refs %d](%s) - %s", id, getClass().getSimpleName(), refCount.get(), creationTime.toString(), description != null ? description : "<null>"));
synchronized(this) {
if (referencedResources.size() > 0) {
builder.append("\n Forward references by Id: ");
for (CrtResource reference : referencedResources) {
builder.append(String.format("%d ", reference.id));
}
}
}
return builder.toString();
}
/**
* Applies a resource description consuming functor to all CRTResource objects
* @param fn function to apply to each resource description
*/
public static void collectNativeResources(Consumer<String> fn) {
collectNativeResource((ResourceInstance resource) -> {
String str = String.format(" * Address: %d: %s", resource.nativeHandle,
resource.toString());
fn.accept(str);
});
}
/**
* Applies a generic diagnostic-gathering functor to all CRTResource objects
* @param fn function to apply to each outstanding Crt resource
*/
public static void collectNativeResource(Consumer<ResourceInstance> fn) {
synchronized(CrtResource.class) {
for (Map.Entry<Long, ResourceInstance> entry : CRT_RESOURCES.entrySet()) {
fn.accept(entry.getValue());
}
}
}
/**
* Debug method to log all of the currently un-closed CRTResource objects.
*/
public static void logNativeResources() {
logNativeResources(ResourceLogLevel);
}
public static void logNativeResources(Log.LogLevel logLevel) {
Log.log(logLevel, Log.LogSubject.JavaCrtResource, "Dumping native object set:");
collectNativeResource((resource) -> {
Log.log(logLevel, Log.LogSubject.JavaCrtResource, resource.getWrapper().getResourceLogDescription());
});
}
/**
* Debug method to increment the current native object count.
*/
private static void incrementNativeObjectCount() {
if (!debugNativeObjects) {
return;
}
lock.lock();
try {
++resourceCount;
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource, String.format("incrementNativeObjectCount - count = %d", resourceCount));
} finally {
lock.unlock();
}
}
/**
* Debug method to decrement the current native object count.
*/
private static void decrementNativeObjectCount() {
if (!debugNativeObjects) {
return;
}
lock.lock();
try {
--resourceCount;
Log.log(ResourceLogLevel, Log.LogSubject.JavaCrtResource, String.format("decrementNativeObjectCount - count = %d", resourceCount));
if (resourceCount == 0) {
emptyResources.signal();
}
} finally {
lock.unlock();
}
}
/**
* Debug/test method to wait for the CRTResource count to drop to zero. Times out with an exception after
* a period of waiting.
*/
public static void waitForNoResources() {
ClientBootstrap.closeStaticDefault();
EventLoopGroup.closeStaticDefault();
HostResolver.closeStaticDefault();
if (debugNativeObjects) {
lock.lock();
try {
long timeout = System.currentTimeMillis() + DEBUG_CLEANUP_WAIT_TIME_IN_SECONDS * 1000;
while (resourceCount != 0 && System.currentTimeMillis() < timeout) {
emptyResources.await(1, TimeUnit.SECONDS);
}
if (resourceCount != 0) {
Log.log(Log.LogLevel.Error, Log.LogSubject.JavaCrtResource, "waitForNoResources - timeOut");
logNativeResources(Log.LogLevel.Error);
throw new InterruptedException();
}
} catch (InterruptedException e) {
/* Cause tests to fail without having to go add checked exceptions to every instance */
throw new RuntimeException("Timeout waiting for resource count to drop to zero");
} finally {
lock.unlock();
}
}
waitForGlobalResourceDestruction(DEBUG_CLEANUP_WAIT_TIME_IN_SECONDS);
}
private static native void waitForGlobalResourceDestruction(int timeoutInSeconds);
}