blob: 4724fae26d053e5c9bec1d67ee9713a64371c03d [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::{Arc, OnceLock};
use std::thread::available_parallelism;
use crate::error::OpenDALError;
use crate::result::OpendalExecutorResult;
use crate::utils::config_invalid_error;
static DEFAULT_EXECUTOR: OnceLock<Arc<Executor>> = OnceLock::new();
pub struct Executor {
runtime: tokio::runtime::Runtime,
}
impl Executor {
fn new(threads: usize) -> Result<Self, OpenDALError> {
if threads == 0 {
return Err(config_invalid_error(
"executor threads must be greater than 0",
));
}
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(threads)
.enable_all()
.build()
.map_err(|e| {
OpenDALError::from_opendal_error(
opendal::Error::new(
opendal::ErrorKind::Unexpected,
"failed to create tokio runtime",
)
.set_source(e),
)
})?;
Ok(Self { runtime })
}
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
self.runtime.block_on(future)
}
pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.runtime.spawn(future)
}
}
fn default_executor() -> Result<Arc<Executor>, OpenDALError> {
if let Some(executor) = DEFAULT_EXECUTOR.get() {
return Ok(executor.clone());
}
let threads = available_parallelism().map(NonZeroUsize::get).unwrap_or(1);
let executor = Arc::new(Executor::new(threads)?);
if DEFAULT_EXECUTOR.set(executor.clone()).is_ok() {
return Ok(executor);
}
if let Some(existing) = DEFAULT_EXECUTOR.get() {
return Ok(existing.clone());
}
Ok(executor)
}
/// Resolve the executor an operator binds at construction.
///
/// # Safety
///
/// `executor` must be either null or a pointer previously returned by
/// `executor_create` that stays alive for the duration of this call. The
/// returned clone keeps the runtime alive on its own afterwards.
pub(crate) unsafe fn executor_or_default(
executor: *const c_void,
) -> Result<Arc<Executor>, OpenDALError> {
if executor.is_null() {
return default_executor();
}
// SAFETY: per the contract above the pointer came from `executor_create`'s
// `Arc::into_raw` and is still alive, so bumping the strong count and
// rebuilding an owned clone is sound.
unsafe {
let executor = executor as *const Executor;
Arc::increment_strong_count(executor);
Ok(Arc::from_raw(executor))
}
}
#[unsafe(no_mangle)]
pub extern "C" fn executor_create(threads: usize) -> OpendalExecutorResult {
match Executor::new(threads) {
Ok(executor) => OpendalExecutorResult::ok(Arc::into_raw(Arc::new(executor)) as *mut c_void),
Err(error) => OpendalExecutorResult::from_error(error),
}
}
/// # Safety
///
/// - `executor` must be either null or a pointer returned by `executor_create`.
/// - The pointer must not be used after this call.
/// - This function must be called at most once for the same pointer.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn executor_free(executor: *mut c_void) {
if executor.is_null() {
return;
}
// SAFETY: reclaims the reference handed out by `executor_create`.
// Operators hold their own clones, so in-flight work keeps its runtime.
unsafe {
drop(Arc::from_raw(executor as *const Executor));
}
}