// 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. #include "exprs/function/function_java_udf.h" #include #include #include #include "common/exception.h" #include "core/block/block.h" #include "format/jni/jni_data_bridge.h" #include "jni.h" #include "runtime/exec_env.h" #include "runtime/user_function_cache.h" #include "util/jni-util.h" const char* EXECUTOR_CLASS = "org/apache/doris/udf/UdfExecutor"; const char* EXECUTOR_CTOR_SIGNATURE = "([B)V"; const char* EXECUTOR_EVALUATE_SIGNATURE = "(Ljava/util/Map;Ljava/util/Map;)J"; const char* EXECUTOR_CLOSE_SIGNATURE = "()V"; namespace doris { JavaFunctionCall::JavaFunctionCall(const TFunction& fn, const DataTypes& argument_types, const DataTypePtr& return_type) : fn_(fn), _argument_types(argument_types), _return_type(return_type) {} Status JavaFunctionCall::open(FunctionContext* context, FunctionContext::FunctionStateScope scope) { JNIEnv* env = nullptr; RETURN_IF_ERROR(Jni::Env::Get(&env)); if (scope == FunctionContext::FunctionStateScope::THREAD_LOCAL) { SCOPED_TIMER(context->get_udf_execute_timer()); std::shared_ptr jni_ctx = std::make_shared(); context->set_function_state(FunctionContext::THREAD_LOCAL, jni_ctx); { std::string local_location; auto function_cache = UserFunctionCache::instance(); TJavaUdfExecutorCtorParams ctor_params; ctor_params.__set_fn(fn_); // get jar path if both file path location and checksum are null if (!fn_.hdfs_location.empty() && !fn_.checksum.empty()) { RETURN_IF_ERROR(function_cache->get_jarpath(fn_.id, fn_.hdfs_location, fn_.checksum, &local_location)); ctor_params.__set_location(local_location); } RETURN_IF_ERROR(Jni::Util::find_class(env, EXECUTOR_CLASS, &jni_ctx->executor_cl)); RETURN_IF_ERROR(jni_ctx->executor_cl.get_method(env, "", EXECUTOR_CTOR_SIGNATURE, &jni_ctx->executor_ctor_id)); RETURN_IF_ERROR(jni_ctx->executor_cl.get_method( env, "evaluate", EXECUTOR_EVALUATE_SIGNATURE, &jni_ctx->executor_evaluate_id)); RETURN_IF_ERROR(jni_ctx->executor_cl.get_method(env, "close", EXECUTOR_CLOSE_SIGNATURE, &jni_ctx->executor_close_id)); Jni::LocalArray ctor_params_bytes; RETURN_IF_ERROR(Jni::Util::SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); RETURN_IF_ERROR(jni_ctx->executor_cl.new_object(env, jni_ctx->executor_ctor_id) .with_arg(ctor_params_bytes) .call(&jni_ctx->executor)); } jni_ctx->open_successes = true; } return Status::OK(); } Status JavaFunctionCall::execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t num_rows) const { JNIEnv* env = nullptr; RETURN_IF_ERROR(Jni::Env::Get(&env)); auto* jni_ctx = reinterpret_cast( context->get_function_state(FunctionContext::THREAD_LOCAL)); SCOPED_TIMER(context->get_udf_execute_timer()); std::unique_ptr input_table; RETURN_IF_ERROR(JniDataBridge::to_java_table(&block, num_rows, arguments, input_table)); auto input_table_schema = JniDataBridge::parse_table_schema(&block, arguments, true); std::map input_params = { {"meta_address", std::to_string((long)input_table.get())}, {"required_fields", input_table_schema.first}, {"columns_types", input_table_schema.second}}; Jni::LocalObject input_map; RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, input_params, &input_map)); auto output_table_schema = JniDataBridge::parse_table_schema(&block, {result}, true); std::string output_nullable = block.get_by_position(result).type->is_nullable() ? "true" : "false"; std::map output_params = {{"is_nullable", output_nullable}, {"required_fields", output_table_schema.first}, {"columns_types", output_table_schema.second}}; Jni::LocalObject output_map; RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, output_params, &output_map)); long output_address = 0; RETURN_IF_ERROR(jni_ctx->executor.call_long_method(env, jni_ctx->executor_evaluate_id) .with_arg(input_map) .with_arg(output_map) .call(&output_address)); return JniDataBridge::fill_block(&block, {result}, output_address); } Status JavaFunctionCall::close(FunctionContext* context, FunctionContext::FunctionStateScope scope) { auto close_func = [context]() { auto* jni_ctx = reinterpret_cast( context->get_function_state(FunctionContext::THREAD_LOCAL)); // JNIContext own some resource and its release method depend on JavaFunctionCall // has to release the resource before JavaFunctionCall is deconstructed. if (jni_ctx) { RETURN_IF_ERROR(jni_ctx->close()); } return Status::OK(); }; if (bthread_self() == 0) { return close_func(); } else { DorisMetrics::instance()->udf_close_bthread_count->increment(1); // Use the close_workers pthread pool to execute the close function auto task = std::make_shared>(std::move(close_func)); auto task_future = task->get_future(); RETURN_IF_ERROR(ExecEnv::GetInstance()->udf_close_workers_pool()->submit_func( [task]() { (*task)(); })); RETURN_IF_ERROR(task_future.get()); return Status::OK(); } } } // namespace doris