blob: f56ce404b654b568aaf85a4866224fcbe3f4863a [file] [log] [blame]
// 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