| // 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. |
| |
| use std::ffi::c_void; |
| use std::future::Future; |
| use std::num::NonZeroUsize; |
| use std::sync::OnceLock; |
| use std::sync::atomic::{AtomicUsize, Ordering}; |
| use std::thread::available_parallelism; |
| |
| use jni::Env; |
| use jni::EnvUnowned; |
| use jni::JavaVM; |
| use jni::jni_sig; |
| use jni::jni_str; |
| use jni::objects::JClass; |
| use jni::objects::JObject; |
| use jni::objects::JValue; |
| use jni::sys::{jint, jlong}; |
| use tokio::task::JoinHandle; |
| |
| use crate::Result; |
| use crate::error::ThrowException; |
| |
| static mut RUNTIME: OnceLock<Executor> = OnceLock::new(); |
| |
| #[unsafe(no_mangle)] |
| pub unsafe extern "system" fn JNI_OnLoad(vm: *mut jni::sys::JavaVM, _: *mut c_void) -> jint { |
| // Register the JavaVM singleton so worker threads can attach to the JVM |
| // later via `JavaVM::singleton()`. |
| let _ = unsafe { JavaVM::from_raw(vm) }; |
| opendal::init_default_registry(); |
| jni::sys::JNI_VERSION_1_8 |
| } |
| |
| /// # Safety |
| /// |
| /// This function could be only called by java vm when unload this lib. |
| #[allow(static_mut_refs)] |
| #[unsafe(no_mangle)] |
| pub unsafe extern "system" fn JNI_OnUnload(_: *mut jni::sys::JavaVM, _: *mut c_void) { |
| unsafe { |
| RUNTIME.take(); |
| } |
| } |
| |
| pub enum Executor { |
| Tokio(tokio::runtime::Runtime), |
| } |
| |
| impl Executor { |
| pub fn enter_with<F, R>(&self, f: F) -> R |
| where |
| F: FnOnce() -> R, |
| { |
| match self { |
| Executor::Tokio(e) => { |
| let _guard = e.enter(); |
| f() |
| } |
| } |
| } |
| |
| pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output> |
| where |
| F: Future + Send + 'static, |
| F::Output: Send + 'static, |
| { |
| match self { |
| Executor::Tokio(e) => e.spawn(future), |
| } |
| } |
| } |
| |
| #[unsafe(no_mangle)] |
| pub extern "system" fn Java_org_apache_opendal_AsyncExecutor_makeTokioExecutor<'local>( |
| mut env: EnvUnowned<'local>, |
| _: JClass<'local>, |
| cores: usize, |
| ) -> jlong { |
| env.with_env(|_env| -> Result<jlong> { |
| let executor = make_tokio_executor(cores)?; |
| Ok(Box::into_raw(Box::new(executor)) as jlong) |
| }) |
| .resolve::<ThrowException>() |
| } |
| |
| /// # Safety |
| /// |
| /// This function should not be called before the AsyncExecutor is ready. |
| #[unsafe(no_mangle)] |
| pub unsafe extern "system" fn Java_org_apache_opendal_AsyncExecutor_disposeInternal<'local>( |
| _: EnvUnowned<'local>, |
| _: JObject<'local>, |
| executor: *mut Executor, |
| ) { |
| unsafe { |
| drop(Box::from_raw(executor)); |
| } |
| } |
| |
| pub(crate) fn make_tokio_executor(cores: usize) -> Result<Executor> { |
| let counter = AtomicUsize::new(0); |
| let executor = tokio::runtime::Builder::new_multi_thread() |
| .worker_threads(cores) |
| .thread_name_fn(move || { |
| let id = counter.fetch_add(1, Ordering::SeqCst); |
| format!("opendal-tokio-worker-{id}") |
| }) |
| .on_thread_start(|| { |
| // `attach_current_thread` creates a permanent attachment; the thread |
| // is detached automatically when it exits. |
| let vm = JavaVM::singleton().expect("JavaVM singleton must be initialized"); |
| vm.attach_current_thread(set_current_thread_name) |
| .expect("attach current thread must succeed"); |
| }) |
| .on_thread_stop(|| { |
| // Typically, the thread attached to the JVM will be detached automatically |
| // when the thread exits. However, there are some edge cases on Windows that |
| // may lead to deadlocks. To mitigate this, we explicitly detach the thread here. |
| // |
| // See https://github.com/apache/opendal/issues/6869 and |
| // https://github.com/jni-rs/jni-rs/issues/701 for more details. |
| if let Ok(vm) = JavaVM::singleton() { |
| let _ = vm.detach_current_thread(); |
| } |
| }) |
| .enable_all() |
| .build() |
| .map_err(|e| { |
| opendal::Error::new( |
| opendal::ErrorKind::Unexpected, |
| "Failed to create tokio runtime.", |
| ) |
| .set_source(e) |
| })?; |
| Ok(Executor::Tokio(executor)) |
| } |
| |
| fn set_current_thread_name(env: &mut Env) -> Result<()> { |
| let current_thread = env |
| .call_static_method( |
| jni_str!("java/lang/Thread"), |
| jni_str!("currentThread"), |
| jni_sig!("()Ljava/lang/Thread;"), |
| &[], |
| )? |
| .l()?; |
| let thread_name = match std::thread::current().name() { |
| Some(thread_name) => env.new_string(thread_name)?, |
| None => unreachable!("thread name must be set"), |
| }; |
| env.call_method( |
| ¤t_thread, |
| jni_str!("setName"), |
| jni_sig!("(Ljava/lang/String;)V"), |
| &[JValue::Object(&thread_name)], |
| )?; |
| Ok(()) |
| } |
| |
| /// # Panic |
| /// |
| /// Crash if the executor is disposed. |
| #[inline] |
| pub(crate) fn executor_or_default(executor: *const Executor) -> Result<&'static Executor> { |
| unsafe { |
| if executor.is_null() { |
| default_executor() |
| } else { |
| // SAFETY: executor must be valid |
| Ok(&*executor) |
| } |
| } |
| } |
| |
| /// # Safety |
| /// |
| /// This function could be only when the lib is loaded. |
| #[allow(static_mut_refs)] |
| unsafe fn default_executor() -> Result<&'static Executor> { |
| // Return the executor if it's already initialized |
| if let Some(runtime) = unsafe { RUNTIME.get() } { |
| return Ok(runtime); |
| } |
| |
| // Try to initialize the executor |
| let executor = |
| make_tokio_executor(available_parallelism().map(NonZeroUsize::get).unwrap_or(1))?; |
| |
| Ok(unsafe { RUNTIME.get_or_init(|| executor) }) |
| } |