-
Notifications
You must be signed in to change notification settings - Fork 5.5k
mobile: check for pending exceptions after JNI call #24361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
cbc1bfc
d74110c
0ef0bd8
7f4f302
7e45442
def2550
b0730d0
7670d9d
e196512
4f5137c
00ba364
125460c
3ad5ae0
ff3266d
57727d3
7ee1e64
f566610
9521741
bda8425
b99b523
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| #include "library/common/jni/jni_utility.h" | ||
| #include "library/common/jni/jni_version.h" | ||
| #include "library/common/main_interface.h" | ||
| #include "library/common/types/managed_envoy_headers.h" | ||
|
|
||
| // NOLINT(namespace-envoy) | ||
|
|
||
|
|
@@ -278,22 +279,23 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordHistogramValue(JNIEnv* en | |
|
|
||
| // JvmCallbackContext | ||
|
|
||
| static void pass_headers(const char* method, envoy_headers headers, jobject j_context) { | ||
| static void pass_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, | ||
| jobject j_context) { | ||
| JNIEnv* env = get_env(); | ||
| jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); | ||
| jmethodID jmid_passHeader = env->GetMethodID(jcls_JvmCallbackContext, method, "([B[BZ)V"); | ||
| jboolean start_headers = JNI_TRUE; | ||
|
|
||
| for (envoy_map_size_t i = 0; i < headers.length; i++) { | ||
| for (envoy_map_size_t i = 0; i < headers.get().length; i++) { | ||
| // Note: this is just an initial implementation, and we will pass a more optimized structure in | ||
| // the future. | ||
| // Note: the JNI function NewStringUTF would appear to be an appealing option here, except it | ||
| // requires a null-terminated *modified* UTF-8 string. | ||
|
|
||
| // Create platform byte array for header key | ||
| jbyteArray j_key = native_data_to_array(env, headers.entries[i].key); | ||
| jbyteArray j_key = native_data_to_array(env, headers.get().entries[i].key); | ||
| // Create platform byte array for header value | ||
| jbyteArray j_value = native_data_to_array(env, headers.entries[i].value); | ||
| jbyteArray j_value = native_data_to_array(env, headers.get().entries[i].value); | ||
|
|
||
| // Pass this header pair to the platform | ||
| env->CallVoidMethod(j_context, jmid_passHeader, j_key, j_value, start_headers); | ||
|
|
@@ -306,15 +308,14 @@ static void pass_headers(const char* method, envoy_headers headers, jobject j_co | |
| } | ||
|
|
||
| env->DeleteLocalRef(jcls_JvmCallbackContext); | ||
| release_envoy_headers(headers); | ||
| } | ||
|
|
||
| // Platform callback implementation | ||
| // These methods call jvm methods which means the local references created will not be | ||
| // released automatically. Manual bookkeeping is required for these methods. | ||
|
|
||
| static void* jvm_on_headers(const char* method, envoy_headers headers, bool end_stream, | ||
| envoy_stream_intel stream_intel, void* context) { | ||
| static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this one isn't called by jvm directly either. optional nit onHeadersFromJvm or something
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's calling a method from the java world tho which I think is why it has a I do not have a strong opinion in here but will keep the things they are until we figure out new naming patterns for this file - ideally in a separate PR. |
||
| bool end_stream, envoy_stream_intel stream_intel, void* context) { | ||
| jni_log("[Envoy]", "jvm_on_headers"); | ||
| JNIEnv* env = get_env(); | ||
| jobject j_context = static_cast<jobject>(context); | ||
|
|
@@ -327,24 +328,50 @@ static void* jvm_on_headers(const char* method, envoy_headers headers, bool end_ | |
| jlongArray j_stream_intel = native_stream_intel_to_array(env, stream_intel); | ||
| // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. | ||
| // TODO: make this cast safer. | ||
| jobject result = env->CallObjectMethod(j_context, jmid_onHeaders, (jlong)headers.length, | ||
| jobject result = env->CallObjectMethod(j_context, jmid_onHeaders, (jlong)headers.get().length, | ||
| end_stream ? JNI_TRUE : JNI_FALSE, j_stream_intel); | ||
| // TODO(Augustyniak): Pass the name of the filter in here so that we can instrument the origin of | ||
| // the JNI exception better. | ||
| if (exception_check(env)) { | ||
| env->DeleteLocalRef(j_stream_intel); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can the DeleteLocalRefs be moved outside of the exception check? alternately if the exception check needs to happen before deletion |
||
| env->DeleteLocalRef(jcls_JvmCallbackContext); | ||
|
|
||
| env->DeleteLocalRef(j_stream_intel); | ||
| env->DeleteLocalRef(jcls_JvmCallbackContext); | ||
| jclass jcls_object_array = env->FindClass("java/lang/Object"); | ||
| jobjectArray result = env->NewObjectArray(2, jcls_object_array, NULL); | ||
|
|
||
| return result; | ||
| jclass jcls_int = env->FindClass("java/lang/Integer"); | ||
| jmethodID jmid_intInit = env->GetMethodID(jcls_int, "<init>", "(I)V"); | ||
| jobject status = env->NewObject(jcls_int, jmid_intInit, 0); | ||
| // Set status to "0" (FilterHeadersStatus::Continue). Signal that the intent | ||
| // is to continue the iteration of the filter chain. | ||
| env->SetObjectArrayElement(result, 0, status); | ||
|
|
||
| // Since the "on headers" call threw an exception set input headers as output headers. | ||
| env->SetObjectArrayElement(result, 1, ToJavaArrayOfObjectArray(env, headers)); | ||
|
|
||
| env->DeleteLocalRef(jcls_object_array); | ||
| env->DeleteLocalRef(jcls_int); | ||
|
|
||
| return result; | ||
| } else { | ||
| env->DeleteLocalRef(j_stream_intel); | ||
| env->DeleteLocalRef(jcls_JvmCallbackContext); | ||
|
|
||
| return result; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to proxy Ryan, let's do this first, and do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you @RyanTheOptimist :) |
||
| } | ||
| } | ||
|
|
||
| static void* jvm_on_response_headers(envoy_headers headers, bool end_stream, | ||
| envoy_stream_intel stream_intel, void* context) { | ||
| return jvm_on_headers("onResponseHeaders", headers, end_stream, stream_intel, context); | ||
| const auto managed_headers = Envoy::Types::ManagedEnvoyHeaders(headers); | ||
| return jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context); | ||
| } | ||
|
|
||
| static envoy_filter_headers_status | ||
| jvm_http_filter_on_request_headers(envoy_headers headers, bool end_stream, | ||
| jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream, | ||
| envoy_stream_intel stream_intel, const void* context) { | ||
| JNIEnv* env = get_env(); | ||
| const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); | ||
| jobjectArray result = static_cast<jobjectArray>(jvm_on_headers( | ||
| "onRequestHeaders", headers, end_stream, stream_intel, const_cast<void*>(context))); | ||
|
|
||
|
|
@@ -363,9 +390,10 @@ jvm_http_filter_on_request_headers(envoy_headers headers, bool end_stream, | |
| } | ||
|
|
||
| static envoy_filter_headers_status | ||
| jvm_http_filter_on_response_headers(envoy_headers headers, bool end_stream, | ||
| jvm_http_filter_on_response_headers(envoy_headers input_headers, bool end_stream, | ||
| envoy_stream_intel stream_intel, const void* context) { | ||
| JNIEnv* env = get_env(); | ||
| const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); | ||
| jobjectArray result = static_cast<jobjectArray>(jvm_on_headers( | ||
| "onResponseHeaders", headers, end_stream, stream_intel, const_cast<void*>(context))); | ||
|
|
||
|
|
@@ -493,6 +521,7 @@ static void* jvm_on_trailers(const char* method, envoy_headers trailers, | |
| jlongArray j_stream_intel = native_stream_intel_to_array(env, stream_intel); | ||
| // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. | ||
| // TODO: make this cast safer. | ||
| // TODO(Augustyniak): check for pending exceptions after returning from JNI call. | ||
| jobject result = | ||
| env->CallObjectMethod(j_context, jmid_onTrailers, (jlong)trailers.length, j_stream_intel); | ||
|
|
||
|
|
@@ -714,6 +743,8 @@ static void* call_jvm_on_complete(envoy_stream_intel stream_intel, | |
| jobject result = | ||
| env->CallObjectMethod(j_context, jmid_onComplete, j_stream_intel, j_final_stream_intel); | ||
|
|
||
| exception_check(env); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment on why it's Ok to clear without handling? |
||
|
|
||
| env->DeleteLocalRef(j_stream_intel); | ||
| env->DeleteLocalRef(j_final_stream_intel); | ||
| env->DeleteLocalRef(jcls_JvmObserverContext); | ||
|
|
@@ -737,6 +768,8 @@ static void* call_jvm_on_error(envoy_error error, envoy_stream_intel stream_inte | |
| jobject result = env->CallObjectMethod(j_context, jmid_onError, error.error_code, j_error_message, | ||
| error.attempt_count, j_stream_intel, j_final_stream_intel); | ||
|
|
||
| exception_check(env); | ||
|
|
||
| env->DeleteLocalRef(j_stream_intel); | ||
| env->DeleteLocalRef(j_final_stream_intel); | ||
| env->DeleteLocalRef(j_error_message); | ||
|
|
@@ -768,6 +801,7 @@ static void* call_jvm_on_cancel(envoy_stream_intel stream_intel, | |
|
|
||
| jobject result = | ||
| env->CallObjectMethod(j_context, jmid_onCancel, j_stream_intel, j_final_stream_intel); | ||
| exception_check(env); | ||
|
|
||
| env->DeleteLocalRef(j_stream_intel); | ||
| env->DeleteLocalRef(j_final_stream_intel); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,16 @@ void jni_delete_const_global_ref(const void* context) { | |
| jni_delete_global_ref(const_cast<void*>(context)); | ||
| } | ||
|
|
||
| bool exception_check(JNIEnv* env) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be inclined to ABSL_MUST_USE_RESULT for this. or at least make it clear we're clearing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think that it always have to be handled explicitly. Per ur suggestion, will change method's name to imply that it clears any pending exceptions |
||
| if (env->ExceptionCheck() == JNI_TRUE) { | ||
| env->ExceptionClear(); | ||
| // TODO(Augustyniak): Log exception details. | ||
| return true; | ||
| } else { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| int unbox_integer(JNIEnv* env, jobject boxedInteger) { | ||
| jclass jcls_Integer = env->FindClass("java/lang/Integer"); | ||
| jmethodID jmid_intValue = env->GetMethodID(jcls_Integer, "intValue", "()I"); | ||
|
|
@@ -278,6 +288,21 @@ envoy_map to_native_map(JNIEnv* env, jobjectArray entries) { | |
| return native_map; | ||
| } | ||
|
|
||
| jobjectArray ToJavaArrayOfObjectArray(JNIEnv* env, const Envoy::Types::ManagedEnvoyHeaders& map) { | ||
| jclass jcls_byte_array = env->FindClass("java/lang/Object"); | ||
| jobjectArray javaArray = env->NewObjectArray(2 * map.get().length, jcls_byte_array, nullptr); | ||
|
|
||
| for (envoy_map_size_t i = 0; i < map.get().length; i++) { | ||
| jbyteArray key = native_data_to_array(env, map.get().entries[i].key); | ||
| jbyteArray value = native_data_to_array(env, map.get().entries[i].value); | ||
|
|
||
| env->SetObjectArrayElement(javaArray, 2 * i, key); | ||
| env->SetObjectArrayElement(javaArray, 2 * i + 1, value); | ||
| } | ||
|
|
||
| return javaArray; | ||
| } | ||
|
|
||
| jobjectArray ToJavaArrayOfByteArray(JNIEnv* env, const std::vector<std::string>& v) { | ||
| jclass jcls_byte_array = env->FindClass("[B"); | ||
| jobjectArray joa = env->NewObjectArray(v.size(), jcls_byte_array, nullptr); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #pragma once | ||
|
|
||
| #include "library/common/types/c_types.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Types { | ||
|
|
||
| /** | ||
| * A wrapper around envoy_headers that's responsible for freeing | ||
| * the underlying headers when they are not needed anymore. | ||
| */ | ||
| class ManagedEnvoyHeaders { | ||
| public: | ||
| /** | ||
| * Initialize a new instance of the receiver using a given instance of envoy headers. | ||
| * | ||
| * @param headers, that should be wrapped by the receiver. The wrapper will hold onto | ||
| * the passed headers and free them once the receiver is not used anymore. | ||
| */ | ||
| ManagedEnvoyHeaders(envoy_headers headers) : headers_(headers){}; | ||
| ~ManagedEnvoyHeaders() { release_envoy_headers(headers_); } | ||
| const envoy_headers& get() const { return headers_; } | ||
|
|
||
| private: | ||
| envoy_headers headers_; | ||
| // Make copy and assignment operators private to prevent copying of the receiver. | ||
| ManagedEnvoyHeaders(const ManagedEnvoyHeaders&); | ||
| ManagedEnvoyHeaders& operator=(const ManagedEnvoyHeaders&); | ||
| }; | ||
|
|
||
| } // namespace Types | ||
| } // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought these all had to be simple (C) types. can you clue me in on why it's OK use ManagedEnvoyHeaders?
edit: nm I think I see why. if this isn't called from Java layers can we use standard passHeaders naming to make that clear?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the usage of c types is a self-imposed limitations in many places in our code. Decided to use c++ type in here to make mem management simpler.
OK