| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| |
| #ifndef IMPALA_UTIL_JNI_UTIL_H |
| #define IMPALA_UTIL_JNI_UTIL_H |
| |
| #include <jni.h> |
| #include <string> |
| #include <vector> |
| |
| #include "common/status.h" |
| #include "gen-cpp/Frontend_types.h" |
| #include "gutil/macros.h" |
| |
| #define THROW_IF_ERROR(stmt, env, impala_exc_cl) \ |
| do { \ |
| const Status& _status = (stmt); \ |
| if (!_status.ok()) { \ |
| (env)->ThrowNew((impala_exc_cl), _status.GetDetail().c_str()); \ |
| return; \ |
| } \ |
| } while (false) |
| |
| #define THROW_IF_ERROR_RET(stmt, env, impala_exc_cl, ret) \ |
| do { \ |
| const Status& _status = (stmt); \ |
| if (!_status.ok()) { \ |
| (env)->ThrowNew((impala_exc_cl), _status.GetDetail().c_str()); \ |
| return (ret); \ |
| } \ |
| } while (false) |
| |
| #define RETURN_ERROR_IF_EXC(env) \ |
| do { \ |
| jthrowable exc = (env)->ExceptionOccurred(); \ |
| if (exc != nullptr) return JniUtil::GetJniExceptionMsg(env);\ |
| } while (false) |
| |
| // If there's an exception in 'env', log the backtrace at FATAL level and abort the |
| // process. This will generate a core dump if core dumps are enabled, so this should |
| // generally only be called for internal errors where the coredump is useful for |
| // diagnostics. |
| #define ABORT_IF_EXC(env) do { ABORT_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); } while (false) |
| |
| // If there's an exception in 'env', log the backtrace at ERROR level and exit the process |
| // cleanly with status 1. |
| #define CLEAN_EXIT_IF_EXC(env) \ |
| do { \ |
| Status s = JniUtil::GetJniExceptionMsg(env); \ |
| if (!s.ok()) CLEAN_EXIT_WITH_ERROR(s.GetDetail()); \ |
| } while (false) |
| |
| namespace impala { |
| |
| class Status; |
| |
| /// Utility class to push/pop a single JNI frame. "push" will push a JNI frame and the |
| /// d'tor will pop the JNI frame. Frames establish a scope for local references. Local |
| /// references go out of scope when their frame is popped, which enables the GC to clean up |
| /// the corresponding objects. |
| class JniLocalFrame { |
| public: |
| JniLocalFrame(): env_(nullptr) {} |
| ~JniLocalFrame() { if (env_ != nullptr) env_->PopLocalFrame(nullptr); } |
| |
| JniLocalFrame(JniLocalFrame&& other) noexcept |
| : env_(other.env_) { |
| other.env_ = nullptr; |
| } |
| |
| /// Pushes a new JNI local frame. The frame can support max_local_ref local references. |
| /// The number of local references created inside the frame might exceed max_local_ref, |
| /// but there is no guarantee that memory will be available. |
| /// Push should be called at most once. |
| Status push(JNIEnv* env, int max_local_ref = 10) WARN_UNUSED_RESULT; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(JniLocalFrame); |
| |
| JNIEnv* env_; |
| }; |
| |
| /// Describes one method to look up in a Java object |
| struct JniMethodDescriptor { |
| /// Name of the method, case must match |
| const std::string name; |
| |
| /// JNI-style method signature |
| const std::string signature; |
| |
| /// Handle to the method |
| jmethodID* method_id; |
| }; |
| |
| /// Helper class for lifetime management of chars from JNI, releasing JNI chars when |
| /// destructed |
| class JniUtfCharGuard { |
| public: |
| /// Construct a JniUtfCharGuards holding nothing |
| JniUtfCharGuard() : utf_chars(nullptr) {} |
| |
| /// Release the held char sequence if there is one. |
| ~JniUtfCharGuard() { |
| if (utf_chars != nullptr) env->ReleaseStringUTFChars(jstr, utf_chars); |
| } |
| |
| /// Try to get chars from jstr. If error is returned, utf_chars and get() remain |
| /// to be nullptr, otherwise they point to a valid char sequence. The char sequence |
| /// lives as long as this guard. jstr should not be null. |
| static Status create(JNIEnv* env, jstring jstr, JniUtfCharGuard* out); |
| |
| /// Get the char sequence. Returns nullptr if the guard does hold a char sequence. |
| const char* get() { return utf_chars; } |
| private: |
| JNIEnv* env; |
| jstring jstr; |
| const char* utf_chars; |
| DISALLOW_COPY_AND_ASSIGN(JniUtfCharGuard); |
| }; |
| |
| class JniScopedArrayCritical { |
| public: |
| /// Construct a JniScopedArrayCritical holding nothing. |
| JniScopedArrayCritical(): env_(nullptr), jarr_(nullptr), arr_(nullptr), size_(0) {} |
| |
| /// Release the held byte[] contents if necessary. |
| ~JniScopedArrayCritical() { |
| if (env_ != nullptr && jarr_ != nullptr && arr_ != nullptr) { |
| env_->ReleasePrimitiveArrayCritical(jarr_, arr_, JNI_ABORT); |
| } |
| } |
| |
| /// Try to get the contents of 'jarr' via JNIEnv::GetPrimitiveArrayCritical() and set |
| /// the results in 'out'. Returns true upon success and false otherwise. If false is |
| /// returned 'out' is not modified. |
| static bool Create(JNIEnv* env, jbyteArray jarr, JniScopedArrayCritical* out) |
| WARN_UNUSED_RESULT; |
| |
| uint8_t* get() const { return arr_; } |
| |
| int size() const { return size_; } |
| private: |
| JNIEnv* env_; |
| jbyteArray jarr_; |
| uint8_t* arr_; |
| int size_; |
| DISALLOW_COPY_AND_ASSIGN(JniScopedArrayCritical); |
| }; |
| |
| /// Utility class for making JNI calls, with various types of argument |
| /// or response. |
| /// |
| /// Example usages: |
| /// |
| /// 1) Static call taking a Thrift struct and returning a string: |
| /// |
| /// string s; |
| /// RETURN_IF_ERROR(JniCall::static_method(my_jclass, my_method) |
| /// .with_thrift_arg(foo).Call(&s)); |
| /// |
| /// 2) Non-static call taking no arguments and returning a Thrift struct: |
| /// |
| /// TMyObject result; |
| /// RETURN_IF_ERROR(JniCall::instance_method(my_jobject, my_method).Call(&result); |
| class JniCall { |
| public: |
| JniCall(JniCall&& other) noexcept = default; |
| |
| static JniCall static_method(jclass clazz, jmethodID method) WARN_UNUSED_RESULT { |
| return JniCall(method, clazz); |
| } |
| |
| static JniCall instance_method(jobject obj, jmethodID method) WARN_UNUSED_RESULT { |
| return JniCall(method, obj); |
| } |
| |
| /// Pass a Thrift-encoded argument. The JNI method should take a byte[] for the |
| /// Thrift-serialized data. Multiple arguments may be passed by repeated calls. |
| template<class T> |
| JniCall& with_thrift_arg(const T& arg) WARN_UNUSED_RESULT; |
| |
| /// Pass a primitive arg (eg an integer). |
| /// Multiple arguments may be passed by repeated calls. |
| template<class T> |
| JniCall& with_primitive_arg(T arg) WARN_UNUSED_RESULT; |
| |
| /// Call the method expecting no result. |
| Status Call() WARN_UNUSED_RESULT { |
| return Call(static_cast<void*>(nullptr)); |
| } |
| |
| /// Call the method and return a result (either std::string or a Thrift struct). |
| template<class T> |
| Status Call(T* result) WARN_UNUSED_RESULT; |
| |
| private: |
| explicit JniCall(jmethodID method); |
| |
| explicit JniCall(jmethodID method, jclass cls) : JniCall(method) { |
| class_ = DCHECK_NOTNULL(cls); |
| } |
| |
| explicit JniCall(jmethodID method, jobject instance) : JniCall(method) { |
| instance_ = DCHECK_NOTNULL(instance); |
| } |
| |
| template<class T> |
| Status ObjectToResult(jobject obj, T* result) WARN_UNUSED_RESULT; |
| |
| Status ObjectToResult(jobject obj, void* no_result) WARN_UNUSED_RESULT; |
| |
| Status ObjectToResult(jobject obj, std::string* result) WARN_UNUSED_RESULT; |
| |
| const jmethodID method_; |
| JNIEnv* const env_; |
| JniLocalFrame frame_; |
| |
| jclass class_ = nullptr; |
| jobject instance_ = nullptr; |
| std::vector<jvalue> args_; |
| Status status_; |
| |
| DISALLOW_COPY_AND_ASSIGN(JniCall); |
| }; |
| |
| |
| /// Utility class for JNI-related functionality. |
| /// Init() should be called as soon as the native library is loaded. |
| /// Creates global class references, and promotes local references to global references. |
| /// Attention! Lifetime of JNI components and common pitfalls: |
| /// 1. JNIEnv* cannot be shared among threads, so it should NOT be globally cached. |
| /// 2. References created via jnienv->New*() calls are local references that go out of scope |
| /// at the end of a code block (and will be gc'ed by the JVM). They should NOT be cached. |
| /// 3. Use global references for caching classes. |
| /// They need to be explicitly created and cleaned up (will not be gc'd up by the JVM). |
| /// Global references can be shared among threads. |
| /// 4. JNI method ids and field ids are tied to the JVM that created them, |
| /// and can be shared among threads. They are not "references" so there is no need |
| /// to explicitly create a global reference to them. |
| class JniUtil { |
| public: |
| /// Init JniUtil. This should be called prior to any other calls. |
| static Status Init() WARN_UNUSED_RESULT; |
| |
| /// Call this prior to any libhdfs calls. |
| static void InitLibhdfs(); |
| |
| /// Returns the JNIEnv attached to the current thread, attaching it |
| /// if necessary. Always returns a valid non-NULL value. |
| static JNIEnv* GetJNIEnv() { |
| if (tls_env_) return tls_env_; |
| return GetJNIEnvSlowPath(); |
| } |
| |
| /// Initializes the JvmPauseMonitor. |
| static Status InitJvmPauseMonitor() WARN_UNUSED_RESULT; |
| |
| /// Returns true if the given class could be found on the CLASSPATH in env. |
| /// Returns false otherwise, or if any other error occurred (e.g. a JNI exception). |
| /// This function does not log any errors or exceptions. |
| static bool ClassExists(JNIEnv* env, const char* class_str); |
| |
| /// Return true if the given class has a non-static method with a specific name and |
| /// signature. Returns false otherwise, or if any other error occurred |
| /// (e.g. a JNI exception). This function does not log any errors or exceptions. |
| static bool MethodExists(JNIEnv* env, jclass class_ref, |
| const char* method_str, const char* method_signature); |
| |
| /// Returns a global JNI reference to the class specified by class_str into class_ref. |
| /// The returned reference must eventually be freed by calling FreeGlobalRef() (or have |
| /// the lifetime of the impalad process). |
| /// Catches Java exceptions and converts their message into status. |
| static Status GetGlobalClassRef( |
| JNIEnv* env, const char* class_str, jclass* class_ref) WARN_UNUSED_RESULT; |
| |
| /// Creates a global reference from a local reference returned into global_ref. |
| /// The returned reference must eventually be freed by calling FreeGlobalRef() (or have |
| /// the lifetime of the impalad process). |
| /// Catches Java exceptions and converts their message into status. |
| static Status LocalToGlobalRef(JNIEnv* env, jobject local_ref, |
| jobject* global_ref) WARN_UNUSED_RESULT; |
| |
| /// Templated wrapper for jobject subclasses (e.g. jclass, jarray). This is necessary |
| /// because according to |
| /// http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html: |
| /// class _jobject {}; |
| /// class _jclass : public _jobject {}; |
| /// ... |
| /// typedef _jobject *jobject; |
| /// typedef _jclass *jclass; |
| /// This mean jobject* is actually _jobject**, so we need the reinterpret_cast in order |
| /// to use a subclass like _jclass**. This is safe in this case because the returned |
| /// subclass is known to be correct. |
| template <typename jobject_subclass> |
| static Status LocalToGlobalRef( |
| JNIEnv* env, jobject local_ref, jobject_subclass* global_ref) { |
| return LocalToGlobalRef(env, local_ref, reinterpret_cast<jobject*>(global_ref)); |
| } |
| |
| static jmethodID throwable_to_string_id() { return throwable_to_string_id_; } |
| static jmethodID throwable_to_stack_trace_id() { return throwable_to_stack_trace_id_; } |
| |
| /// Returns true if an embedded JVM is initialized, false otherwise. |
| static bool is_jvm_inited() { return jvm_inited_; } |
| |
| /// Global reference to java JniUtil class |
| static jclass jni_util_class() { return jni_util_cl_; } |
| |
| /// Global reference to InternalException class. |
| static jclass internal_exc_class() { return internal_exc_cl_; } |
| |
| /// Returns the error message for 'e'. If no exception, returns Status::OK |
| /// log_stack determines if the stack trace is written to the log |
| /// prefix, if non-empty will be prepended to the error message. |
| static Status GetJniExceptionMsg(JNIEnv* env, bool log_stack = true, |
| const std::string& prefix = "") WARN_UNUSED_RESULT; |
| |
| /// Populates 'result' with a list of memory metrics from the Jvm. Returns Status::OK |
| /// unless there is an exception. |
| static Status GetJvmMemoryMetrics( |
| TGetJvmMemoryMetricsResponse* result) WARN_UNUSED_RESULT; |
| |
| /// Populates 'result' with information about live JVM threads. Returns |
| /// Status::OK unless there is an exception. |
| static Status GetJvmThreadsInfo(const TGetJvmThreadsInfoRequest& request, |
| TGetJvmThreadsInfoResponse* result) WARN_UNUSED_RESULT; |
| |
| /// Gets JMX metrics of the JVM encoded as a JSON string. |
| static Status GetJMXJson(TGetJMXJsonResponse* result) WARN_UNUSED_RESULT; |
| |
| /// Loads a method whose signature is in the supplied descriptor. Returns Status::OK |
| /// and sets descriptor->method_id to a JNI method handle if successful, otherwise an |
| /// error status is returned. |
| static Status LoadJniMethod(JNIEnv* jni_env, const jclass& jni_class, |
| JniMethodDescriptor* descriptor) WARN_UNUSED_RESULT; |
| |
| /// Same as LoadJniMethod(...), except that this loads a static method. |
| static Status LoadStaticJniMethod(JNIEnv* jni_env, const jclass& jni_class, |
| JniMethodDescriptor* descriptor) WARN_UNUSED_RESULT; |
| |
| /// Utility methods to avoid repeating lots of the JNI call boilerplate. |
| /// New code should prefer using JniCall() directly for better clarity. |
| static Status CallJniMethod( |
| const jobject& obj, const jmethodID& method) WARN_UNUSED_RESULT { |
| return JniCall::instance_method(obj, method).Call(); |
| } |
| |
| static Status CallStaticJniMethod( |
| const jclass& cls, const jmethodID& method) WARN_UNUSED_RESULT { |
| return JniCall::static_method(cls, method).Call(); |
| } |
| |
| template <typename T> |
| static Status CallJniMethod(const jobject& obj, const jmethodID& method, |
| const T& arg) WARN_UNUSED_RESULT; |
| |
| template <typename T, typename R> |
| static Status CallJniMethod(const jobject& obj, const jmethodID& method, |
| const T& arg, R* response) WARN_UNUSED_RESULT; |
| |
| template <typename R> |
| static Status CallJniMethod(const jobject& obj, const jmethodID& method, |
| R* response) WARN_UNUSED_RESULT; |
| |
| private: |
| // Slow-path for GetJNIEnv, used on the first call by any thread. |
| static JNIEnv* GetJNIEnvSlowPath(); |
| |
| // Set in Init() once the JVM is initialized. |
| static bool jvm_inited_; |
| static jclass jni_util_cl_; |
| static jclass internal_exc_cl_; |
| static jmethodID throwable_to_string_id_; |
| static jmethodID throwable_to_stack_trace_id_; |
| static jmethodID get_jvm_metrics_id_; |
| static jmethodID get_jvm_threads_id_; |
| static jmethodID get_jmx_json_; |
| |
| // Thread-local cache of the JNIEnv for this thread. |
| static __thread JNIEnv* tls_env_; |
| }; |
| |
| /// Convert a C++ primitive to a JNI 'jvalue' union. |
| /// See https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html |
| /// for reference on the union members. |
| template<typename T> |
| jvalue PrimitiveToValue(T cpp_val); |
| |
| #define SPECIALIZE_PRIMITIVE_TO_VALUE(cpp_type, union_field) \ |
| template<> inline jvalue PrimitiveToValue(cpp_type cpp_val) { \ |
| jvalue v; \ |
| memset(&v, 0, sizeof(v)); \ |
| v.union_field = cpp_val; \ |
| return v; \ |
| } |
| SPECIALIZE_PRIMITIVE_TO_VALUE(bool, z); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(int8_t, b); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(char, c); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(int16_t, s); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(int32_t, i); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(int64_t, j); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(float, f); |
| SPECIALIZE_PRIMITIVE_TO_VALUE(double, d); |
| #undef SPECIALIZE_PRIMITIVE_TO_VALUE |
| |
| template <typename T> |
| inline Status JniUtil::CallJniMethod(const jobject& obj, const jmethodID& method, |
| const T& arg) { |
| return JniCall::instance_method(obj, method).with_thrift_arg(arg).Call(); |
| } |
| |
| template <> |
| inline Status JniUtil::CallJniMethod<int64_t>(const jobject& obj, const jmethodID& method, |
| const int64_t& arg) { |
| return JniCall::instance_method(obj, method).with_primitive_arg(arg).Call(); |
| } |
| |
| template <typename T, typename R> |
| inline Status JniUtil::CallJniMethod(const jobject& obj, const jmethodID& method, |
| const T& arg, R* response) { |
| return JniCall::instance_method(obj, method).with_thrift_arg(arg).Call(response); |
| } |
| |
| template <typename R> |
| inline Status JniUtil::CallJniMethod(const jobject& obj, const jmethodID& method, |
| R* response) { |
| return JniCall::instance_method(obj, method).Call(response); |
| } |
| |
| inline JniCall::JniCall(jmethodID method) |
| : method_(method), |
| env_(JniUtil::GetJNIEnv()) { |
| status_ = frame_.push(env_); |
| } |
| |
| template<class T> |
| inline JniCall& JniCall::with_thrift_arg(const T& arg) { |
| if (!status_.ok()) return *this; |
| jbyteArray bytes; |
| status_ = SerializeThriftMsg(env_, &arg, &bytes); |
| if (status_.ok()) { |
| jvalue arg; |
| memset(&arg, 0, sizeof(arg)); |
| arg.l = bytes; |
| args_.emplace_back(arg); |
| } |
| return *this; |
| } |
| template<class T> |
| inline JniCall& JniCall::with_primitive_arg(T arg) { |
| if (!status_.ok()) return *this; |
| args_.emplace_back(PrimitiveToValue(arg)); |
| return *this; |
| } |
| |
| template<class T> |
| inline Status JniCall::Call(T* result) { |
| RETURN_IF_ERROR(status_); |
| DCHECK((instance_ != nullptr) ^ (class_ != nullptr)); |
| |
| // Even if the function takes no arguments, it's OK to pass an array here. |
| // The JNI API doesn't take a length and just assumes that you've passed |
| // an appropriate number of elements. |
| jobject ret; |
| if (class_) { |
| ret = env_->CallStaticObjectMethodA(class_, method_, args_.data()); |
| } else { |
| ret = env_->CallObjectMethodA(instance_, method_, args_.data()); |
| } |
| RETURN_ERROR_IF_EXC(env_); |
| RETURN_IF_ERROR(ObjectToResult(ret, result)); |
| return Status::OK(); |
| } |
| |
| template<class T> |
| inline Status JniCall::ObjectToResult(jobject obj, T* result) { |
| DCHECK(obj) << "Call returned unexpected null Thrift object"; |
| RETURN_IF_ERROR(DeserializeThriftMsg(env_, static_cast<jbyteArray>(obj), result)); |
| return Status::OK(); |
| } |
| |
| inline Status JniCall::ObjectToResult(jobject obj, void* no_result) { |
| return Status::OK(); |
| } |
| |
| inline Status JniCall::ObjectToResult(jobject obj, std::string* result) { |
| DCHECK(obj) << "Call returned unexpected null String instance"; |
| JniUtfCharGuard utf; |
| RETURN_IF_ERROR(JniUtfCharGuard::create(env_, static_cast<jstring>(obj), &utf)); |
| *result = utf.get();; |
| return Status::OK(); |
| } |
| |
| } |
| |
| #endif |