Skip to content

Commit

Permalink
java: create native resource management system (#1138)
Browse files Browse the repository at this point in the history
Description: Adds the general-purpose capability for java objects to be associated with and manage native resources tied to the lifecycle of said object. Also introduces filter callbacks as the first use case for this system.
Risk Level: Moderate
Testing: Pending (#1139)

Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: JP Simard <[email protected]>
  • Loading branch information
goaway authored and jpsim committed Nov 29, 2022
1 parent ae96353 commit d2c96c7
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ java_library(
"EnvoyConfiguration.java",
"EnvoyEngine.java",
"EnvoyEngineImpl.java",
"EnvoyHTTPFilterCallbacks.java",
"EnvoyHTTPFilterCallbacksImpl.java",
"EnvoyHTTPStream.java",
"EnvoyNativeResourceRegistry.java",
"EnvoyNativeResourceReleaser.java",
"EnvoyNativeResourceWrapper.java",
"JniBridgeUtility.java",
"JniLibrary.java",
"JvmBridgeUtility.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.envoyproxy.envoymobile.engine;

public interface EnvoyHTTPFilterCallbacks { void resumeIteration(); }
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.envoyproxy.envoymobile.engine;

import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceReleaser;
import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceWrapper;

final class EnvoyHTTPFilterCallbacksImpl
implements EnvoyHTTPFilterCallbacks, EnvoyNativeResourceWrapper {

private static final EnvoyNativeResourceReleaser releaseCallbacks = (long handle) -> {
callReleaseCallbacks(handle);
};

private final long callbackHandle;

/**
* @param callbackHandle, native handle for callback execution. This must be eventually passed to
`callReleaseCallbacks` to release underlying memory.
*/
EnvoyHTTPFilterCallbacksImpl(long callbackHandle) { this.callbackHandle = callbackHandle; }

static EnvoyHTTPFilterCallbacksImpl create(long callbackHandle) {
final EnvoyHTTPFilterCallbacksImpl object = new EnvoyHTTPFilterCallbacksImpl(callbackHandle);
EnvoyNativeResourceRegistry.globalRegister(object, callbackHandle, releaseCallbacks);
return object;
}

public void resumeIteration() { callResumeIteration(callbackHandle, this); }

/**
* @param callbackHandle, native handle for callback execution.
* @param object, pass this object so that the JNI retains it, preventing it from potentially
* being concurrently garbage-collected while the native call is executing.
*/
private native void callResumeIteration(long callbackHandle, EnvoyHTTPFilterCallbacksImpl object);

private static native void callReleaseCallbacks(long callbackHandle);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.envoyproxy.envoymobile.engine;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceReleaser;
import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceWrapper;

/**
* Central class to manage releasing native resources when wrapper objects are flagged as
* unreachable by the garbage collector.
*/
public enum EnvoyNativeResourceRegistry {
SINGLETON;

private ReferenceQueue
refQueue; // References are automatically enqueued when the gc flags them as unreachable.
private Collection refMaintainer; // Maintains references in the object graph while we wait for
// them to be enqueued.

private class QueueThread extends Thread {
public void run() {
while (EnvoyPhantomRef releasable = (EnvoyPhantomRef)refQueue.remove()) {
releasable.releaseResource();
refMaintainer.remove(releasable);
}
}
}

private class EnvoyPhantomRef extends PhantomReference<EnvoyNativeResourceWrapper> {
private final EnvoyNativeResourceReleaser releaser;
private final long nativeHandle;

EnvoyPhantomRef(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
super(owner, refQueue);
this.nativeHandle = nativeHandle;
this.releaser = releaser;
}

releaseResource() { releaser.run(nativeHandle); }
}

/**
* Register an EnvoyNativeResourceWrapper to schedule cleanup of its native resources when the
* Java object is flagged for collection by the garbage collector.
*/
public void register(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
EnvoyPhantomRef ref = new EnvoyPhantomRef(owner, nativeHandle, releaser);
refMaintainer.add(ref);
}

public static void register(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
SINGLETON.register(owner, nativeHandle, releaser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.envoyproxy.envoymobile.engine;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;

import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceReleaser;
import io.envoyproxy.envoymobile.engine.EnvoyNativeResourceWrapper;

/**
* Central class to manage releasing native resources when wrapper objects are flagged as
* unreachable by the garbage collector.
*/
public enum EnvoyNativeResourceRegistry {
SINGLETON;

// References are automatically enqueued when the gc flags them as unreachable.
private ReferenceQueue refQueue;
// Maintains references in the object graph while we wait for them to be enqueued.
private Set refMaintainer;
// Blocks on the reference queue and calls the releaser of queued references.
private RefQueueThread refQueueThread;

private class RefQueueThread extends Thread {
public void run() {
EnvoyPhantomRef ref;
while (true) {
try {
ref = (EnvoyPhantomRef)refQueue.remove();
} catch (InterruptedException e) {
continue;
}

ref.releaseResource();
refMaintainer.remove(ref);
}
}
}

private class EnvoyPhantomRef extends PhantomReference<EnvoyNativeResourceWrapper> {
private final EnvoyNativeResourceReleaser releaser;
private final long nativeHandle;

EnvoyPhantomRef(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
super(owner, refQueue);
this.nativeHandle = nativeHandle;
this.releaser = releaser;
refQueueThread = new RefQueueThread();
refMaintainer = new ConcurrentHashMap().newKeySet();
refQueueThread.start();
}

void releaseResource() { releaser.release(nativeHandle); }
}

/**
* Register an EnvoyNativeResourceWrapper to schedule cleanup of its native resources when the
* Java object is flagged for collection by the garbage collector.
*
* @param owner, The object that has retained the native resource.
* @param nativeHandle, An opaque identifier for the native resource.
* @param releaser, A lambda that makes the native call to release the resource.
*/
public void register(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
EnvoyPhantomRef ref = new EnvoyPhantomRef(owner, nativeHandle, releaser);
refMaintainer.add(ref);
}

/**
* Register an EnvoyNativeResourceWrapper to schedule cleanup of its native resources when the
* Java object is flagged for collection by the garbage collector.
*
* @param owner, The object that has retained the native resource.
* @param nativeHandle, An opaque identifier for the native resource.
* @param releaser, A lambda that makes the native call to release the resource.
*/
public static void globalRegister(EnvoyNativeResourceWrapper owner, long nativeHandle,
EnvoyNativeResourceReleaser releaser) {
SINGLETON.register(owner, nativeHandle, releaser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.envoyproxy.envoymobile.engine;

public interface EnvoyNativeResourceReleaser {

/**
* Release a native resource held by a Java object.
*
* @param @nativeHandle, JNI identifier for the native resource.
*/
public void release(long nativeHandle);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.envoyproxy.envoymobile.engine;

/**
* This interface exists solely to scope the types accepted by EnvoyNativeResourceRegistry.
*
* Implemented by objects that require a native resource to be tied to their lifecycle.
*/
interface EnvoyNativeResourceWrapper {}
4 changes: 4 additions & 0 deletions mobile/library/proguard.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
<methods>;
}

-keep, includedescriptorclasses class io.envoyproxy.envoymobile.engine.EnvoyHTTPFilterCallbacksImpl {
<methods>;
}

-keep, includedescriptorclasses class io.envoyproxy.envoymobile.engine.JvmBridgeUtility {
<methods>;
}
Expand Down

0 comments on commit d2c96c7

Please sign in to comment.