blob: 3f15688e7e5150df220a391679dc51ceecd8fa35 [file]
// 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(
&current_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) })
}