This is an automated email from the ASF dual-hosted git repository.

taiyang-li pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 26f04d815c [GLUTEN-12535][CORE] Support backend-specific JNI input 
adapters (#12535)
26f04d815c is described below

commit 26f04d815c28c4253fe693a02c3b69f4b385ba58
Author: wangxinshuo-bolt <[email protected]>
AuthorDate: Fri Jul 24 14:10:04 2026 +0800

    [GLUTEN-12535][CORE] Support backend-specific JNI input adapters (#12535)
    
    * [CORE] Add reusable JNI input adapters for backend runtimes
    
    * Remove useless test
    
    fix
    
    * fix
    
    * [CORE] Decouple JNI input adapters from Runtime
    
    * [CORE] Move shuffle stream JNI bridge to JniCommon
---
 cpp/core/jni/JniCommon.cc  | 180 +++++++++++++++++++++++++++++++++++++++++++++
 cpp/core/jni/JniCommon.h   |  39 ++++++++++
 cpp/core/jni/JniWrapper.cc | 131 +--------------------------------
 3 files changed, 220 insertions(+), 130 deletions(-)

diff --git a/cpp/core/jni/JniCommon.cc b/cpp/core/jni/JniCommon.cc
index 76a07aa2d8..1f57e25a17 100644
--- a/cpp/core/jni/JniCommon.cc
+++ b/cpp/core/jni/JniCommon.cc
@@ -18,6 +18,99 @@
 #include "JniCommon.h"
 #include <folly/system/ThreadName.h>
 
+#include "utils/ArrowStatus.h"
+
+namespace {
+
+std::unordered_map<std::string, gluten::JniInputIteratorFactory>& 
jniInputIteratorFactories() {
+  static std::unordered_map<std::string, gluten::JniInputIteratorFactory> 
factories;
+  return factories;
+}
+
+std::mutex& jniInputIteratorFactoriesMutex() {
+  static std::mutex mutex;
+  return mutex;
+}
+
+class JavaInputStreamAdaptor final : public arrow::io::InputStream {
+ public:
+  JavaInputStreamAdaptor(JNIEnv* env, arrow::MemoryPool* pool, jobject jniIn) 
: pool_(pool) {
+    // IMPORTANT: DO NOT USE LOCAL REF IN DIFFERENT THREAD
+    if (env->GetJavaVM(&vm_) != JNI_OK) {
+      std::string errorMessage = "Unable to get JavaVM instance";
+      throw gluten::GlutenException(errorMessage);
+    }
+    jniIn_ = env->NewGlobalRef(jniIn);
+  }
+
+  ~JavaInputStreamAdaptor() override {
+    try {
+      auto status = JavaInputStreamAdaptor::Close();
+      if (!status.ok()) {
+        LOG(WARNING) << __func__ << " call JavaInputStreamAdaptor::Close() 
failed, status:" << status.ToString();
+      }
+    } catch (std::exception& e) {
+      LOG(WARNING) << __func__ << " call JavaInputStreamAdaptor::Close() got 
exception:" << e.what();
+    }
+  }
+
+  // not thread safe
+  arrow::Status Close() override {
+    if (closed_) {
+      return arrow::Status::OK();
+    }
+    JNIEnv* env;
+    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
+    env->CallVoidMethod(jniIn_, 
gluten::getJniCommonState()->jniByteInputStreamClose());
+    checkException(env);
+    env->DeleteGlobalRef(jniIn_);
+    // Do NOT call DetachCurrentThread() here.
+    // libhdfs.so caches JNIEnv* in thread-local storage after 
AttachCurrentThread.
+    // If we detach, libhdfs's TLS cache becomes stale — the next HDFS call via
+    // libhdfs returns the stale env, causing SIGSEGV in jni_NewStringUTF.
+    // Daemon-attached threads are safe to leave attached; they won't block 
JVM shutdown.
+    closed_ = true;
+    return arrow::Status::OK();
+  }
+
+  arrow::Result<int64_t> Tell() const override {
+    JNIEnv* env;
+    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
+    jlong told = env->CallLongMethod(jniIn_, 
gluten::getJniCommonState()->jniByteInputStreamTell());
+    checkException(env);
+    return told;
+  }
+
+  bool closed() const override {
+    return closed_;
+  }
+
+  arrow::Result<int64_t> Read(int64_t nbytes, void* out) override {
+    JNIEnv* env;
+    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
+    jlong read = env->CallLongMethod(
+        jniIn_, gluten::getJniCommonState()->jniByteInputStreamRead(), 
reinterpret_cast<jlong>(out), nbytes);
+    checkException(env);
+    return read;
+  }
+
+  arrow::Result<std::shared_ptr<arrow::Buffer>> Read(int64_t nbytes) override {
+    GLUTEN_ASSIGN_OR_THROW(auto buffer, arrow::AllocateResizableBuffer(nbytes, 
pool_))
+    GLUTEN_ASSIGN_OR_THROW(int64_t bytes_read, Read(nbytes, 
buffer->mutable_data()))
+    GLUTEN_THROW_NOT_OK(buffer->Resize(bytes_read, false));
+    buffer->ZeroPadding();
+    return std::move(buffer);
+  }
+
+ private:
+  arrow::MemoryPool* pool_;
+  JavaVM* vm_;
+  jobject jniIn_;
+  bool closed_ = false;
+};
+
+} // namespace
+
 void gluten::JniCommonState::ensureInitialized(JNIEnv* env) {
   std::lock_guard<std::mutex> lockGuard(mtx_);
   if (initialized_) {
@@ -38,9 +131,41 @@ jmethodID gluten::JniCommonState::runtimeAwareCtxHandle() {
   return runtimeAwareCtxHandle_;
 }
 
+jmethodID gluten::JniCommonState::jniByteInputStreamRead() {
+  assertInitialized();
+  return jniByteInputStreamRead_;
+}
+
+jmethodID gluten::JniCommonState::jniByteInputStreamTell() {
+  assertInitialized();
+  return jniByteInputStreamTell_;
+}
+
+jmethodID gluten::JniCommonState::jniByteInputStreamClose() {
+  assertInitialized();
+  return jniByteInputStreamClose_;
+}
+
+jmethodID gluten::JniCommonState::shuffleStreamReaderNextStream() {
+  assertInitialized();
+  return shuffleStreamReaderNextStream_;
+}
+
 void gluten::JniCommonState::initialize(JNIEnv* env) {
   runtimeAwareClass_ = createGlobalClassReference(env, 
"Lorg/apache/gluten/runtime/RuntimeAware;");
   runtimeAwareCtxHandle_ = getMethodIdOrError(env, runtimeAwareClass_, 
"rtHandle", "()J");
+
+  jniByteInputStreamClass_ =
+      createGlobalClassReferenceOrError(env, 
"Lorg/apache/gluten/vectorized/JniByteInputStream;");
+  jniByteInputStreamRead_ = getMethodIdOrError(env, jniByteInputStreamClass_, 
"read", "(JJ)J");
+  jniByteInputStreamTell_ = getMethodIdOrError(env, jniByteInputStreamClass_, 
"tell", "()J");
+  jniByteInputStreamClose_ = getMethodIdOrError(env, jniByteInputStreamClass_, 
"close", "()V");
+
+  shuffleStreamReaderClass_ =
+      createGlobalClassReferenceOrError(env, 
"Lorg/apache/gluten/vectorized/ShuffleStreamReader;");
+  shuffleStreamReaderNextStream_ = getMethodIdOrError(
+      env, shuffleStreamReaderClass_, "nextStream", 
"()Lorg/apache/gluten/vectorized/JniByteInputStream;");
+
   JavaVM* vm;
   if (env->GetJavaVM(&vm) != JNI_OK) {
     throw gluten::GlutenException("Unable to get JavaVM instance");
@@ -56,6 +181,8 @@ void gluten::JniCommonState::close() {
   JNIEnv* env = nullptr;
   attachCurrentThreadAsDaemonOrThrow(vm_, &env);
   env->DeleteGlobalRef(runtimeAwareClass_);
+  env->DeleteGlobalRef(jniByteInputStreamClass_);
+  env->DeleteGlobalRef(shuffleStreamReaderClass_);
   closed_ = true;
 }
 
@@ -67,6 +194,59 @@ gluten::Runtime* gluten::getRuntime(JNIEnv* env, jobject 
runtimeAware) {
   return ctx;
 }
 
+void gluten::registerJniInputIteratorFactory(const std::string& runtimeKind, 
JniInputIteratorFactory factory) {
+  GLUTEN_CHECK(!runtimeKind.empty(), "JNI input iterator factory runtime kind 
must not be empty");
+  GLUTEN_CHECK(static_cast<bool>(factory), "JNI input iterator factory must 
not be empty");
+
+  std::lock_guard<std::mutex> lock(jniInputIteratorFactoriesMutex());
+  const bool inserted = jniInputIteratorFactories().emplace(runtimeKind, 
std::move(factory)).second;
+  GLUTEN_CHECK(inserted, "JNI input iterator factory already registered for " 
+ runtimeKind);
+}
+
+std::unique_ptr<gluten::ColumnarBatchIterator>
+gluten::createJniInputIterator(JNIEnv* env, jobject iterator, Runtime* 
runtime, int32_t iteratorIndex) {
+  GLUTEN_CHECK(runtime != nullptr, "Runtime must not be null");
+
+  JniInputIteratorFactory factory;
+  {
+    std::lock_guard<std::mutex> lock(jniInputIteratorFactoriesMutex());
+    const auto it = jniInputIteratorFactories().find(runtime->kind());
+    if (it != jniInputIteratorFactories().end()) {
+      factory = it->second;
+    }
+  }
+
+  if (factory) {
+    return factory(env, iterator, runtime, iteratorIndex);
+  }
+  return std::make_unique<JniColumnarBatchIterator>(env, iterator, runtime, 
iteratorIndex);
+}
+
+gluten::ShuffleStreamReader::ShuffleStreamReader(JNIEnv* env, jobject reader) {
+  if (env->GetJavaVM(&vm_) != JNI_OK) {
+    throw GlutenException("Unable to get JavaVM instance");
+  }
+  ref_ = env->NewGlobalRef(reader);
+}
+
+gluten::ShuffleStreamReader::~ShuffleStreamReader() {
+  JNIEnv* env = nullptr;
+  attachCurrentThreadAsDaemonOrThrow(vm_, &env);
+  env->DeleteGlobalRef(ref_);
+}
+
+std::shared_ptr<arrow::io::InputStream> 
gluten::ShuffleStreamReader::readNextStream(arrow::MemoryPool* pool) {
+  JNIEnv* env = nullptr;
+  attachCurrentThreadAsDaemonOrThrow(vm_, &env);
+
+  jobject jniIn = env->CallObjectMethod(ref_, 
getJniCommonState()->shuffleStreamReaderNextStream());
+  checkException(env);
+  if (jniIn == nullptr) {
+    return nullptr; // No more streams to read
+  }
+  return std::make_shared<JavaInputStreamAdaptor>(env, pool, jniIn);
+}
+
 std::unique_ptr<gluten::JniColumnarBatchIterator>
 gluten::makeJniColumnarBatchIterator(JNIEnv* env, jobject jColumnarBatchItr, 
gluten::Runtime* runtime) {
   return std::make_unique<JniColumnarBatchIterator>(env, jColumnarBatchItr, 
runtime);
diff --git a/cpp/core/jni/JniCommon.h b/cpp/core/jni/JniCommon.h
index bf5a6a746b..f1113a7f82 100644
--- a/cpp/core/jni/JniCommon.h
+++ b/cpp/core/jni/JniCommon.h
@@ -22,9 +22,12 @@
 #include <execinfo.h>
 #include <jni.h>
 
+#include <functional>
+
 #include "compute/ProtobufUtils.h"
 #include "compute/Runtime.h"
 #include "memory/AllocationListener.h"
+#include "shuffle/ShuffleReader.h"
 #include "shuffle/rss/RssClient.h"
 #include "threads/ThreadInitializer.h"
 #include "utils/Compression.h"
@@ -151,6 +154,18 @@ static T* jniCastOrThrow(jlong handle) {
 }
 namespace gluten {
 
+class ShuffleStreamReader final : public StreamReader {
+ public:
+  ShuffleStreamReader(JNIEnv* env, jobject reader);
+  ~ShuffleStreamReader() override;
+
+  std::shared_ptr<arrow::io::InputStream> readNextStream(arrow::MemoryPool* 
pool) override;
+
+ private:
+  JavaVM* vm_{nullptr};
+  jobject ref_{nullptr};
+};
+
 class JniCommonState {
  public:
   virtual ~JniCommonState() = default;
@@ -163,6 +178,14 @@ class JniCommonState {
 
   jmethodID runtimeAwareCtxHandle();
 
+  jmethodID jniByteInputStreamRead();
+
+  jmethodID jniByteInputStreamTell();
+
+  jmethodID jniByteInputStreamClose();
+
+  jmethodID shuffleStreamReaderNextStream();
+
   JavaVM* getJavaVM() const {
     return vm_;
   }
@@ -173,6 +196,14 @@ class JniCommonState {
   jclass runtimeAwareClass_;
   jmethodID runtimeAwareCtxHandle_;
 
+  jclass jniByteInputStreamClass_;
+  jmethodID jniByteInputStreamRead_;
+  jmethodID jniByteInputStreamTell_;
+  jmethodID jniByteInputStreamClose_;
+
+  jclass shuffleStreamReaderClass_;
+  jmethodID shuffleStreamReaderNextStream_;
+
   JavaVM* vm_;
   bool initialized_{false};
   bool closed_{false};
@@ -186,6 +217,14 @@ inline JniCommonState* getJniCommonState() {
 
 Runtime* getRuntime(JNIEnv* env, jobject runtimeAware);
 
+using JniInputIteratorFactory = std::function<
+    std::unique_ptr<ColumnarBatchIterator>(JNIEnv* env, jobject iterator, 
Runtime* runtime, int32_t iteratorIndex)>;
+
+void registerJniInputIteratorFactory(const std::string& runtimeKind, 
JniInputIteratorFactory factory);
+
+std::unique_ptr<ColumnarBatchIterator>
+createJniInputIterator(JNIEnv* env, jobject iterator, Runtime* runtime, 
int32_t iteratorIndex);
+
 // Safe version of JNI {Get|Release}<PrimitiveType>ArrayElements routines.
 // SafeNativeArray would release the managed array elements automatically
 // during destruction.
diff --git a/cpp/core/jni/JniWrapper.cc b/cpp/core/jni/JniWrapper.cc
index e4d674bd74..91b35c822e 100644
--- a/cpp/core/jni/JniWrapper.cc
+++ b/cpp/core/jni/JniWrapper.cc
@@ -27,7 +27,6 @@
 #include "shuffle/ShuffleReader.h"
 #include "shuffle/ShuffleWriter.h"
 #include "shuffle/Utils.h"
-#include "utils/ArrowStatus.h"
 #include "utils/StringUtil.h"
 
 #include <arrow/c/bridge.h>
@@ -51,11 +50,6 @@ jmethodID jniUnsafeByteBufferAllocate;
 jmethodID jniUnsafeByteBufferAddress;
 jmethodID jniUnsafeByteBufferSize;
 
-jclass jniByteInputStreamClass;
-jmethodID jniByteInputStreamRead;
-jmethodID jniByteInputStreamTell;
-jmethodID jniByteInputStreamClose;
-
 jclass splitResultClass;
 jmethodID splitResultConstructor;
 
@@ -68,9 +62,6 @@ jclass shuffleReaderMetricsClass;
 jmethodID shuffleReaderMetricsSetDecompressTime;
 jmethodID shuffleReaderMetricsSetDeserializeTime;
 
-jclass shuffleStreamReaderClass;
-jmethodID shuffleStreamReaderNextStream;
-
 jbyteArray toJByteArray(JNIEnv* env, const std::vector<uint8_t>& bytes, const 
std::string& context) {
   GLUTEN_CHECK(
       bytes.size() <= static_cast<size_t>(std::numeric_limits<jsize>::max()),
@@ -82,82 +73,6 @@ jbyteArray toJByteArray(JNIEnv* env, const 
std::vector<uint8_t>& bytes, const st
   return out;
 }
 
-class JavaInputStreamAdaptor final : public arrow::io::InputStream {
- public:
-  JavaInputStreamAdaptor(JNIEnv* env, arrow::MemoryPool* pool, jobject jniIn) 
: pool_(pool) {
-    // IMPORTANT: DO NOT USE LOCAL REF IN DIFFERENT THREAD
-    if (env->GetJavaVM(&vm_) != JNI_OK) {
-      std::string errorMessage = "Unable to get JavaVM instance";
-      throw GlutenException(errorMessage);
-    }
-    jniIn_ = env->NewGlobalRef(jniIn);
-  }
-
-  ~JavaInputStreamAdaptor() override {
-    try {
-      auto status = JavaInputStreamAdaptor::Close();
-      if (!status.ok()) {
-        LOG(WARNING) << __func__ << " call JavaInputStreamAdaptor::Close() 
failed, status:" << status.ToString();
-      }
-    } catch (std::exception& e) {
-      LOG(WARNING) << __func__ << " call JavaInputStreamAdaptor::Close() got 
exception:" << e.what();
-    }
-  }
-
-  // not thread safe
-  arrow::Status Close() override {
-    if (closed_) {
-      return arrow::Status::OK();
-    }
-    JNIEnv* env;
-    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
-    env->CallVoidMethod(jniIn_, jniByteInputStreamClose);
-    checkException(env);
-    env->DeleteGlobalRef(jniIn_);
-    // Do NOT call DetachCurrentThread() here.
-    // libhdfs.so caches JNIEnv* in thread-local storage after 
AttachCurrentThread.
-    // If we detach, libhdfs's TLS cache becomes stale — the next HDFS call via
-    // libhdfs returns the stale env, causing SIGSEGV in jni_NewStringUTF.
-    // Daemon-attached threads are safe to leave attached; they won't block 
JVM shutdown.
-    closed_ = true;
-    return arrow::Status::OK();
-  }
-
-  arrow::Result<int64_t> Tell() const override {
-    JNIEnv* env;
-    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
-    jlong told = env->CallLongMethod(jniIn_, jniByteInputStreamTell);
-    checkException(env);
-    return told;
-  }
-
-  bool closed() const override {
-    return closed_;
-  }
-
-  arrow::Result<int64_t> Read(int64_t nbytes, void* out) override {
-    JNIEnv* env;
-    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
-    jlong read = env->CallLongMethod(jniIn_, jniByteInputStreamRead, 
reinterpret_cast<jlong>(out), nbytes);
-    checkException(env);
-    return read;
-  }
-
-  arrow::Result<std::shared_ptr<arrow::Buffer>> Read(int64_t nbytes) override {
-    GLUTEN_ASSIGN_OR_THROW(auto buffer, arrow::AllocateResizableBuffer(nbytes, 
pool_))
-    GLUTEN_ASSIGN_OR_THROW(int64_t bytes_read, Read(nbytes, 
buffer->mutable_data()))
-    GLUTEN_THROW_NOT_OK(buffer->Resize(bytes_read, false));
-    buffer->ZeroPadding();
-    return std::move(buffer);
-  }
-
- private:
-  arrow::MemoryPool* pool_;
-  JavaVM* vm_;
-  jobject jniIn_;
-  bool closed_ = false;
-};
-
 /// Internal backend consists of empty implementations of Runtime API and 
MemoryManager API.
 /// The backend is used for saving contextual objects only.
 ///
@@ -238,39 +153,6 @@ void internalRuntimeReleaser(Runtime* runtime) {
   delete runtime;
 }
 
-class ShuffleStreamReader : public StreamReader {
- public:
-  ShuffleStreamReader(JNIEnv* env, jobject reader) {
-    if (env->GetJavaVM(&vm_) != JNI_OK) {
-      throw GlutenException("Unable to get JavaVM instance");
-    }
-    ref_ = env->NewGlobalRef(reader);
-  }
-
-  ~ShuffleStreamReader() override {
-    JNIEnv* env = nullptr;
-    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
-    env->DeleteGlobalRef(ref_);
-  }
-
-  std::shared_ptr<arrow::io::InputStream> readNextStream(arrow::MemoryPool* 
pool) override {
-    JNIEnv* env = nullptr;
-    attachCurrentThreadAsDaemonOrThrow(vm_, &env);
-
-    jobject jniIn = env->CallObjectMethod(ref_, shuffleStreamReaderNextStream);
-    checkException(env);
-    if (jniIn == nullptr) {
-      return nullptr; // No more streams to read
-    }
-    std::shared_ptr<arrow::io::InputStream> in = 
std::make_shared<JavaInputStreamAdaptor>(env, pool, jniIn);
-    return in;
-  }
-
- private:
-  JavaVM* vm_;
-  jobject ref_;
-};
-
 } // namespace
 
 #ifdef __cplusplus
@@ -298,11 +180,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
   jniUnsafeByteBufferAddress = env->GetMethodID(jniUnsafeByteBufferClass, 
"address", "()J");
   jniUnsafeByteBufferSize = env->GetMethodID(jniUnsafeByteBufferClass, "size", 
"()J");
 
-  jniByteInputStreamClass = createGlobalClassReferenceOrError(env, 
"Lorg/apache/gluten/vectorized/JniByteInputStream;");
-  jniByteInputStreamRead = getMethodIdOrError(env, jniByteInputStreamClass, 
"read", "(JJ)J");
-  jniByteInputStreamTell = getMethodIdOrError(env, jniByteInputStreamClass, 
"tell", "()J");
-  jniByteInputStreamClose = getMethodIdOrError(env, jniByteInputStreamClass, 
"close", "()V");
-
   splitResultClass = createGlobalClassReferenceOrError(env, 
"Lorg/apache/gluten/vectorized/GlutenSplitResult;");
   splitResultConstructor = getMethodIdOrError(env, splitResultClass, "<init>", 
"(JJJJJJJJJJDJ[J[J[J)V");
 
@@ -322,18 +199,12 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
   shuffleReaderMetricsSetDeserializeTime =
       getMethodIdOrError(env, shuffleReaderMetricsClass, "setDeserializeTime", 
"(J)V");
 
-  shuffleStreamReaderClass =
-      createGlobalClassReferenceOrError(env, 
"Lorg/apache/gluten/vectorized/ShuffleStreamReader;");
-  shuffleStreamReaderNextStream = getMethodIdOrError(
-      env, shuffleStreamReaderClass, "nextStream", 
"()Lorg/apache/gluten/vectorized/JniByteInputStream;");
-
   return jniVersion;
 }
 
 void JNI_OnUnload(JavaVM* vm, void* reserved) {
   JNIEnv* env;
   vm->GetEnv(reinterpret_cast<void**>(&env), jniVersion);
-  env->DeleteGlobalRef(jniByteInputStreamClass);
   env->DeleteGlobalRef(splitResultClass);
   env->DeleteGlobalRef(nativeColumnarToRowInfoClass);
   env->DeleteGlobalRef(byteArrayClass);
@@ -566,7 +437,7 @@ 
Java_org_apache_gluten_vectorized_PlanEvaluatorJniWrapper_nativeCreateKernelWith
     inputIters.reserve(itersLen);
     for (int idx = 0; idx < itersLen; idx++) {
       jobject iter = env->GetObjectArrayElement(batchItrArray, idx);
-      auto arrayIter = std::make_unique<JniColumnarBatchIterator>(env, iter, 
ctx, idx);
+      auto arrayIter = createJniInputIterator(env, iter, ctx, idx);
       auto resultIter = std::make_shared<ResultIterator>(std::move(arrayIter));
       inputIters.push_back(std::move(resultIter));
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to