blob: df332951afebb8ac4389e6a908bfa7e2a80ec7ba [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.
#pragma once
#include <butil/macros.h>
#include <jni.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <unordered_map>
#include "common/status.h"
#include "jni_md.h"
#include "util/defer_op.h"
#include "util/thrift_util.h"
#ifdef USE_HADOOP_HDFS
// defined in hadoop/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c
extern "C" JNIEnv* getJNIEnv(void);
#endif
namespace doris {
class JniUtil;
#define RETURN_ERROR_IF_EXC(env) \
do { \
if (env->ExceptionCheck()) [[unlikely]] \
return JniUtil::GetJniExceptionMsg(env); \
} while (false)
#define JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF(type, result, env, func) \
type result = env->func; \
DEFER(env->DeleteLocalRef(result)); \
RETURN_ERROR_IF_EXC(env)
#define JNI_CALL_METHOD_CHECK_EXCEPTION(type, result, env, func) \
type result = env->func; \
RETURN_ERROR_IF_EXC(env)
//In order to reduce the potential risks caused by not handling exceptions,
// you need to refer to https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
// to confirm whether the jni method will throw an exception.
class JniUtil {
public:
static Status Init() WARN_UNUSED_RESULT;
static jmethodID throwable_to_string_id() { return throwable_to_string_id_; }
static Status GetJNIEnv(JNIEnv** env) {
if (tls_env_) {
*env = tls_env_;
} else {
Status status = GetJNIEnvSlowPath(env);
if (!status.ok()) {
return status;
}
}
if (*env == nullptr) {
return Status::RuntimeError("Failed to get JNIEnv: it is nullptr.");
}
return Status::OK();
}
//jclass is generally a local reference.
//Method ID and field ID values are forever.
//If you want to use the jclass across multiple threads or multiple calls into the JNI code you need
// to create a global reference to it with GetGlobalClassRef().
static Status GetGlobalClassRef(JNIEnv* env, const char* class_str,
jclass* class_ref) WARN_UNUSED_RESULT;
static Status LocalToGlobalRef(JNIEnv* env, jobject local_ref,
jobject* global_ref) WARN_UNUSED_RESULT;
static Status GetJniExceptionMsg(JNIEnv* env, bool log_stack = true,
const std::string& prefix = "") WARN_UNUSED_RESULT;
static jclass jni_util_class() { return jni_util_cl_; }
static jmethodID throwable_to_stack_trace_id() { return throwable_to_stack_trace_id_; }
static const int64_t INITIAL_RESERVED_BUFFER_SIZE = 1024;
// TODO: we need a heuristic strategy to increase buffer size for variable-size output.
static inline int64_t IncreaseReservedBufferSize(int n) {
return INITIAL_RESERVED_BUFFER_SIZE << n;
}
static Status get_jni_scanner_class(JNIEnv* env, const char* classname, jclass* loaded_class);
static jobject convert_to_java_map(JNIEnv* env, const std::map<std::string, std::string>& map);
static std::map<std::string, std::string> convert_to_cpp_map(JNIEnv* env, jobject map);
static size_t get_max_jni_heap_memory_size();
static Status clean_udf_class_load_cache(const std::string& function_signature);
private:
static void parse_max_heap_memory_size_from_jvm(JNIEnv* env);
static Status GetJNIEnvSlowPath(JNIEnv** env);
static Status init_jni_scanner_loader(JNIEnv* env);
static bool jvm_inited_;
static jclass internal_exc_cl_;
static jclass jni_native_method_exc_cl_;
static jclass jni_util_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_;
// JNI scanner loader
static jobject jni_scanner_loader_obj_;
static jmethodID jni_scanner_loader_method_;
// Thread-local cache of the JNIEnv for this thread.
static __thread JNIEnv* tls_env_;
static jlong max_jvm_heap_memory_size_;
static jmethodID _clean_udf_cache_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 = nullptr;
jstring jstr;
const char* utf_chars = nullptr;
DISALLOW_COPY_AND_ASSIGN(JniUtfCharGuard);
};
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_ = nullptr;
};
template <class T>
Status SerializeThriftMsg(JNIEnv* env, T* msg, jbyteArray* serialized_msg) {
int buffer_size = 100 * 1024; // start out with 100KB
ThriftSerializer serializer(false, buffer_size);
uint8_t* buffer = nullptr;
uint32_t size = 0;
RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer));
// Make sure that 'size' is within the limit of INT_MAX as the use of
// 'size' below takes int.
if (size > INT_MAX) {
return Status::InternalError(
"The length of the serialization buffer ({} bytes) exceeds the limit of {} bytes",
size, INT_MAX);
}
/// create jbyteArray given buffer
*serialized_msg = env->NewByteArray(size);
RETURN_ERROR_IF_EXC(env);
if (*serialized_msg == NULL) return Status::InternalError("couldn't construct jbyteArray");
env->SetByteArrayRegion(*serialized_msg, 0, size, reinterpret_cast<jbyte*>(buffer));
RETURN_ERROR_IF_EXC(env);
return Status::OK();
}
} // namespace doris