Skip to content

Commit

Permalink
lib: make Android interface instance-based and threadsafe (#149)
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Schore <mike.schore@gmail.com>

Description: Updates Android interface to statically load and initialize the library, but allow multiple Envoy instances to be created with their own configuration and tied to their own thread.
Risk Level: Moderate
Testing: Built and ran library and apps locally

Signed-off-by: JP Simard <jp@jpsim.com>
  • Loading branch information
goaway authored and jpsim committed Nov 29, 2022
1 parent 4d05a8e commit 1cf46f8
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 22 deletions.
11 changes: 7 additions & 4 deletions mobile/examples/java/hello_world/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,25 @@ public class MainActivity extends Activity {

private HandlerThread thread = new HandlerThread(REQUEST_HANDLER_THREAD_NAME);

private Envoy envoy;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Load an envoy config, and run envoy on a separate thread.
Envoy envoy = new Envoy();
envoy.load();
Context context = getBaseContext();
Envoy.load(context);

// Create envoy instance with config.
String config = null;
try {
config = loadEnvoyConfig(getBaseContext(), R.raw.config);
} catch (RuntimeException e) {
Log.d("MainActivity", "exception getting config.", e);
throw new RuntimeException("Can't get config to run envoy.");
}
envoy.run(getBaseContext(), config);
envoy = new Envoy(context, config);

recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
Expand Down
9 changes: 5 additions & 4 deletions mobile/examples/kotlin/hello_world/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ private const val ENVOY_SERVER_HEADER = "server"
class MainActivity : Activity() {
private lateinit var recyclerView: RecyclerView
private val thread = HandlerThread(REQUEST_HANDLER_THREAD_NAME)
private lateinit var envoy: Envoy

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Load an envoy config, and run envoy on a separate thread.
val envoy = Envoy()
envoy.load()
envoy.run(baseContext, loadEnvoyConfig(baseContext, R.raw.config))
Envoy.load(baseContext)

// Create Envoy instance with config.
envoy = Envoy(baseContext, loadEnvoyConfig(baseContext, R.raw.config))

recyclerView = findViewById(R.id.recycler_view) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(this)
Expand Down
10 changes: 5 additions & 5 deletions mobile/library/common/jni_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
return JNI_VERSION_1_6;
}

extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_Envoy_runEnvoy(JNIEnv* env,
jobject, // this
jstring config) {
extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_Envoy_run(JNIEnv* env,
jobject, // this
jstring config) {
return run_envoy(env->GetStringUTFChars(config, nullptr));
}

extern "C" JNIEXPORT jint JNICALL
Java_io_envoyproxy_envoymobile_Envoy_initialize(JNIEnv* env,
jobject, // this
jclass, // class
jobject connectivity_manager) {
// See note above about c-ares.
return ares_library_init_android(connectivity_manager);
}

extern "C" JNIEXPORT jboolean JNICALL Java_io_envoyproxy_envoymobile_Envoy_isAresInitialized(
JNIEnv* env,
jobject // this
jclass // class
) {
return ares_library_android_initialized() == ARES_SUCCESS;
}
63 changes: 54 additions & 9 deletions mobile/library/java/io/envoyproxy/envoymobile/Envoy.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,67 @@
// Wrapper class that allows for easy calling of Envoy's JNI interface in native Java.
public class Envoy {

public void load() { System.loadLibrary("envoy_jni"); }
// Internal reference to helper object used to load and initialize the native library.
// Volatile to ensure double-checked locking works correctly.
private static volatile EnvoyLoader loader = null;

public void run(Context context, String config) {
Thread thread = new Thread(new Runnable() {
// Dedicated thread for running this instance of Envoy.
private final Thread runner;

// Private helper class used by the load method to ensure the native library and its
// dependencies are loaded and initialized at most once.
private static class EnvoyLoader {

EnvoyLoader(Context context) {
System.loadLibrary("envoy_jni");
initialize((ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE));
}
}

// Load and initialize Envoy and its dependencies, but only once.
public static void load(Context context) {
if (loader != null) {
return;
}

synchronized (Envoy.class) {
if (loader != null) {
return;
}

loader = new EnvoyLoader(context);
}
}

// Create a new Envoy instance. The Envoy runner Thread is started as part of instance
// initialization with the configuration provided. If the Envoy native library and its
// dependencies haven't been loaded and initialized yet, this will happen lazily when
// the first instance is created.
public Envoy(Context context, String config) {
// Lazily initialize Envoy and its dependencies, if necessary.
load(context);

runner = new Thread(new Runnable() {
@Override
public void run() {
initialize((ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE));
runEnvoy(config.trim());
Envoy.this.run(config.trim());
}
});
thread.start();
runner.start();
}

private native int initialize(ConnectivityManager connectivityManager);
// Returns whether the Envoy instance is currently active and running.
public boolean isRunning() {
final Thread.State state = runner.getState();
return state != Thread.State.NEW && state != Thread.State.TERMINATED;
}

// Returns whether the Envoy instance is terminated.
public boolean isTerminated() { return runner.getState() == Thread.State.TERMINATED; }

private static native int initialize(ConnectivityManager connectivityManager);

private native boolean isAresInitialized();
private static native boolean isAresInitialized();

private native int runEnvoy(String config);
private native int run(String config);
}

0 comments on commit 1cf46f8

Please sign in to comment.