diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml index b0f791fa9dcd..41d57428324f 100644 --- a/bindings/c/Cargo.toml +++ b/bindings/c/Cargo.toml @@ -35,8 +35,9 @@ cbindgen = "0.29.0" [dependencies] bytes = "1.4.0" +futures-util = "0.3.32" # this crate won't be published, we always use the local version -opendal = { version = ">=0", path = "../../core", features = ["blocking"] } +opendal = { version = ">=0", path = "../../core" } tokio = { version = "1.27", features = ["fs", "macros", "rt-multi-thread"] } [features] diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 4bc85d59765c..311d3e75471d 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -137,6 +137,17 @@ typedef struct opendal_error { struct opendal_bytes message; } opendal_error; +/** + * \brief A cancellation token for cancellable OpenDAL operations. + */ +typedef struct opendal_cancel_token { + /** + * The pointer to the Rust cancellation token. + * Only touch this on judging whether it is NULL. + */ + void *inner; +} opendal_cancel_token; + /** * \brief opendal_list_entry is the entry under a path, which is listed from the opendal_lister * @@ -253,14 +264,14 @@ typedef struct opendal_metadata_user_metadata_pair { * @see opendal_operator_free This function frees the heap memory of the operator * * \note The opendal_operator actually owns a pointer to - * an opendal::blocking::Operator, which is inside the Rust core code. + * an opendal::Operator, which is inside the Rust core code. * * \remark You may use the field `ptr` to check whether this is a NULL * operator. */ typedef struct opendal_operator { /** - * The pointer to the opendal::blocking::Operator in the Rust code. + * The pointer to the operator state in the Rust code. * Only touch this on judging whether it is NULL. */ void *inner; @@ -495,11 +506,11 @@ typedef struct opendal_read_options { * \brief The result type returned by opendal's reader operation. * * \note The opendal_reader actually owns a pointer to - * a opendal::BlockingReader, which is inside the Rust core code. + * a opendal::FuturesAsyncReader, which is inside the Rust core code. */ typedef struct opendal_reader { /** - * The pointer to the opendal::StdReader in the Rust code. + * The pointer to the opendal::FuturesAsyncReader in the Rust code. * Only touch this on judging whether it is NULL. */ void *inner; @@ -1140,6 +1151,29 @@ extern "C" { */ void opendal_error_free(struct opendal_error *ptr); +/** + * \brief Construct a cancellation token. + */ +struct opendal_cancel_token *opendal_cancel_token_new(void); + +/** + * \brief Cancel operations using this token. + */ +void opendal_cancel_token_cancel(const struct opendal_cancel_token *ptr); + +/** + * \brief Free a cancellation token. + */ +void opendal_cancel_token_free(struct opendal_cancel_token *ptr); + +/** + * \brief Like `opendal_lister_next` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_lister_next opendal_lister_next_with_cancel(struct opendal_lister *self, + const struct opendal_cancel_token *token); + /** * \brief Return the next object to be listed * @@ -1381,6 +1415,197 @@ struct opendal_result_operator_new opendal_operator_new_with_layers(const char * const struct opendal_operator_options *options, const struct opendal_operator_layers *layers); +/** + * \brief Like `opendal_operator_write` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_write_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_write_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_write_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes, + const struct opendal_write_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_read` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_read opendal_operator_read_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_read_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_read opendal_operator_read_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_read_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_reader` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_operator_reader opendal_operator_reader_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_writer` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_operator_writer opendal_operator_writer_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_writer_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_operator_writer opendal_operator_writer_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_write_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_delete` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_delete_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_delete_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_delete_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_delete_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_is_exist` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_is_exist opendal_operator_is_exist_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_exists` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_exists opendal_operator_exists_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_stat` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_stat opendal_operator_stat_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_stat_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_stat opendal_operator_stat_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_stat_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_list` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_list opendal_operator_list_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_list_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_list opendal_operator_list_with_options_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_list_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_create_dir` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_rename` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_rename_with_cancel(const struct opendal_operator *op, + const char *src, + const char *dest, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_copy` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_copy_with_cancel(const struct opendal_operator *op, + const char *src, + const char *dest, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_copy_with` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_copy_with_options_cancel(const struct opendal_operator *op, + const char *src, + const char *dest, + const struct opendal_copy_options *opts, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_check` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_operator_check_with_cancel(const struct opendal_operator *op, + const struct opendal_cancel_token *token); + /** * \brief Blocking write raw bytes to `path`. * @@ -2159,6 +2384,46 @@ struct opendal_capability opendal_operator_info_get_full_capability(const struct */ struct opendal_capability opendal_operator_info_get_native_capability(const struct opendal_operator_info *self); +/** + * \brief Like `opendal_operator_presign_read` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_presign opendal_operator_presign_read_with_cancel(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_presign_write` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_presign opendal_operator_presign_write_with_cancel(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_presign_delete` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_presign opendal_operator_presign_delete_with_cancel(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs, + const struct opendal_cancel_token *token); + +/** + * \brief Like `opendal_operator_presign_stat` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_presign opendal_operator_presign_stat_with_cancel(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs, + const struct opendal_cancel_token *token); + /** * \brief Presign a read operation. */ @@ -2650,6 +2915,16 @@ struct opendal_metadata *opendal_entry_metadata(const struct opendal_entry *self */ void opendal_entry_free(struct opendal_entry *ptr); +/** + * \brief Like `opendal_reader_read` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_reader_read opendal_reader_read_with_cancel(struct opendal_reader *self, + uint8_t *buf, + uintptr_t len, + const struct opendal_cancel_token *token); + /** * \brief Read data from the reader. */ @@ -2657,6 +2932,16 @@ struct opendal_result_reader_read opendal_reader_read(struct opendal_reader *sel uint8_t *buf, uintptr_t len); +/** + * \brief Like `opendal_reader_seek` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_reader_seek opendal_reader_seek_with_cancel(struct opendal_reader *self, + int64_t offset, + int32_t whence, + const struct opendal_cancel_token *token); + /** * \brief Seek to an offset, in bytes, in a stream. */ @@ -2669,12 +2954,29 @@ struct opendal_result_reader_seek opendal_reader_seek(struct opendal_reader *sel */ void opendal_reader_free(struct opendal_reader *ptr); +/** + * \brief Like `opendal_writer_write` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_result_writer_write opendal_writer_write_with_cancel(struct opendal_writer *self, + const struct opendal_bytes *bytes, + const struct opendal_cancel_token *token); + /** * \brief Write data to the writer. */ struct opendal_result_writer_write opendal_writer_write(struct opendal_writer *self, const struct opendal_bytes *bytes); +/** + * \brief Like `opendal_writer_close` with cooperative cancellation. + * + * Pass NULL for `token` to block until completion. + */ +struct opendal_error *opendal_writer_close_with_cancel(struct opendal_writer *ptr, + const struct opendal_cancel_token *token); + /** * \brief Close the writer and make sure all data have been stored. */ diff --git a/bindings/c/src/cancel.rs b/bindings/c/src/cancel.rs new file mode 100644 index 000000000000..eefd53b998bb --- /dev/null +++ b/bindings/c/src/cancel.rs @@ -0,0 +1,197 @@ +// 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::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; + +use ::opendal as core; + +use crate::runtime::RUNTIME; + +struct CancelState { + cancelled: AtomicBool, + waker: Mutex>, +} + +impl CancelState { + fn new() -> Self { + Self { + cancelled: AtomicBool::new(false), + waker: Mutex::new(None), + } + } + + fn cancel(&self) { + self.cancelled.store(true, Ordering::SeqCst); + if let Some(waker) = self.waker.lock().expect("cancel waker poisoned").take() { + waker.wake(); + } + } +} + +#[derive(Clone)] +pub(crate) struct CancelToken { + state: Arc, +} + +impl CancelToken { + fn new() -> Self { + Self { + state: Arc::new(CancelState::new()), + } + } + + fn cancel(&self) { + self.state.cancel(); + } + + fn cancelled(&self) -> Cancelled { + Cancelled { + state: self.state.clone(), + } + } +} + +pub(crate) struct Cancelled { + state: Arc, +} + +impl Future for Cancelled { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.state.cancelled.load(Ordering::SeqCst) { + return Poll::Ready(()); + } + + *self.state.waker.lock().expect("cancel waker poisoned") = Some(cx.waker().clone()); + + if self.state.cancelled.load(Ordering::SeqCst) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +/// \brief A cancellation token for cancellable OpenDAL operations. +#[repr(C)] +pub struct opendal_cancel_token { + /// The pointer to the Rust cancellation token. + /// Only touch this on judging whether it is NULL. + inner: *mut c_void, +} + +impl opendal_cancel_token { + fn deref(&self) -> &CancelToken { + // Safety: inner is initialized by opendal_cancel_token_new. + unsafe { &*(self.inner as *mut CancelToken) } + } + + /// \brief Construct a cancellation token. + #[no_mangle] + pub unsafe extern "C" fn opendal_cancel_token_new() -> *mut Self { + Box::into_raw(Box::new(Self { + inner: Box::into_raw(Box::new(CancelToken::new())) as _, + })) + } + + /// \brief Cancel operations using this token. + #[no_mangle] + pub unsafe extern "C" fn opendal_cancel_token_cancel(ptr: *const Self) { + if !ptr.is_null() { + unsafe { (*ptr).deref().cancel() }; + } + } + + /// \brief Free a cancellation token. + #[no_mangle] + pub unsafe extern "C" fn opendal_cancel_token_free(ptr: *mut Self) { + if !ptr.is_null() { + unsafe { + drop(Box::from_raw((*ptr).inner as *mut CancelToken)); + drop(Box::from_raw(ptr)); + } + } + } +} + +pub(crate) unsafe fn clone_token(ptr: *const opendal_cancel_token) -> Option { + if ptr.is_null() { + None + } else { + Some(unsafe { (*ptr).deref().clone() }) + } +} + +pub(crate) async fn run(token: Option, fut: F) -> core::Result +where + F: Future>, +{ + match token { + Some(token) => { + tokio::select! { + biased; + _ = token.cancelled() => Err(cancelled_error()), + result = fut => result, + } + } + None => fut.await, + } +} + +pub(crate) fn cancelled_error() -> core::Error { + core::Error::new(core::ErrorKind::Unexpected, "operation cancelled") +} + +/// Block on a cancellable future on the current runtime thread. +/// +/// Use this for futures that capture non-`Send` borrows, such as reader/writer/lister +/// handles accessed through `&mut self`. +pub(crate) fn block_on_cancelable( + token: *const opendal_cancel_token, + fut: F, +) -> core::Result +where + F: Future>, +{ + let token = unsafe { clone_token(token) }; + RUNTIME.block_on(run(token, fut)) +} + +/// Block on a cancellable `Send` future by spawning it on the runtime. +/// +/// Use this for operator-level calls where the future owns cloned `Operator` state. +pub(crate) fn block_on_cancelable_spawn( + token: *const opendal_cancel_token, + fut: F, +) -> core::Result +where + T: Send + 'static, + F: Future> + Send + 'static, +{ + let token = unsafe { clone_token(token) }; + RUNTIME + .block_on(RUNTIME.spawn(run(token, fut))) + .map_err(|err| { + core::Error::new(core::ErrorKind::Unexpected, "cancellable task failed").set_source(err) + })? +} diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index 1ef4148a2d83..d469d611e8c1 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -36,6 +36,11 @@ mod error; pub use error::opendal_code; pub use error::opendal_error; +mod runtime; + +mod cancel; +pub use cancel::opendal_cancel_token; + mod lister; pub use lister::opendal_lister; diff --git a/bindings/c/src/lister.rs b/bindings/c/src/lister.rs index 94950b148090..36c80d246fa6 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -16,6 +16,7 @@ // under the License. use ::opendal as core; +use futures_util::StreamExt; use std::ffi::c_void; use super::*; @@ -35,20 +36,51 @@ pub struct opendal_lister { } impl opendal_lister { - fn deref_mut(&mut self) -> &mut core::blocking::Lister { + fn deref_mut(&mut self) -> &mut core::Lister { // Safety: the inner should never be null once constructed // The use-after-free is undefined behavior - unsafe { &mut *(self.inner as *mut core::blocking::Lister) } + unsafe { &mut *(self.inner as *mut core::Lister) } } } impl opendal_lister { - pub(crate) fn new(lister: core::blocking::Lister) -> Self { + pub(crate) fn new_async(lister: core::Lister) -> Self { Self { inner: Box::into_raw(Box::new(lister)) as _, } } + fn result_next(result: core::Result>) -> opendal_result_lister_next { + match result { + Ok(Some(e)) => opendal_result_lister_next { + entry: Box::into_raw(Box::new(opendal_entry::new(e))), + error: std::ptr::null_mut(), + }, + Ok(None) => opendal_result_lister_next { + entry: std::ptr::null_mut(), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_lister_next { + entry: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } + } + + /// \brief Like `opendal_lister_next` with cooperative cancellation. + /// + /// Pass NULL for `token` to block until completion. + #[no_mangle] + pub unsafe extern "C" fn opendal_lister_next_with_cancel( + &mut self, + token: *const opendal_cancel_token, + ) -> opendal_result_lister_next { + let lister = self.deref_mut(); + Self::result_next(cancel::block_on_cancelable(token, async move { + lister.next().await.transpose() + })) + } + /// \brief Return the next object to be listed /// /// Lister is an iterator of the objects under its path, this method is the same as @@ -58,27 +90,7 @@ impl opendal_lister { /// @see opendal_operator_list() #[no_mangle] pub unsafe extern "C" fn opendal_lister_next(&mut self) -> opendal_result_lister_next { - let e = self.deref_mut().next(); - if e.is_none() { - return opendal_result_lister_next { - entry: std::ptr::null_mut(), - error: std::ptr::null_mut(), - }; - } - - match e.unwrap() { - Ok(e) => { - let ent = Box::into_raw(Box::new(opendal_entry::new(e))); - opendal_result_lister_next { - entry: ent, - error: std::ptr::null_mut(), - } - } - Err(e) => opendal_result_lister_next { - entry: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { Self::opendal_lister_next_with_cancel(self, std::ptr::null()) } } /// \brief Free the heap-allocated metadata used by opendal_lister @@ -86,7 +98,7 @@ impl opendal_lister { pub unsafe extern "C" fn opendal_lister_free(ptr: *mut opendal_lister) { unsafe { if !ptr.is_null() { - drop(Box::from_raw((*ptr).inner as *mut core::blocking::Lister)); + drop(Box::from_raw((*ptr).inner as *mut core::Lister)); drop(Box::from_raw(ptr)); } } diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index cb610640242f..4a3c7164e2eb 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -18,19 +18,11 @@ use std::collections::HashMap; use std::ffi::c_void; use std::os::raw::c_char; -use std::sync::LazyLock; use ::opendal as core; use super::*; -static RUNTIME: LazyLock = LazyLock::new(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() -}); - /// \brief Used to access almost all OpenDAL APIs. It represents an /// operator that provides the unified interfaces provided by OpenDAL. /// @@ -38,22 +30,22 @@ static RUNTIME: LazyLock = LazyLock::new(|| { /// @see opendal_operator_free This function frees the heap memory of the operator /// /// \note The opendal_operator actually owns a pointer to -/// an opendal::blocking::Operator, which is inside the Rust core code. +/// an opendal::Operator, which is inside the Rust core code. /// /// \remark You may use the field `ptr` to check whether this is a NULL /// operator. #[repr(C)] pub struct opendal_operator { - /// The pointer to the opendal::blocking::Operator in the Rust code. + /// The pointer to the operator state in the Rust code. /// Only touch this on judging whether it is NULL. inner: *mut c_void, } impl opendal_operator { - pub(crate) fn deref(&self) -> &core::blocking::Operator { + pub(crate) fn deref(&self) -> &core::Operator { // Safety: the inner should never be null once constructed // The use-after-free is undefined behavior - unsafe { &*(self.inner as *mut core::blocking::Operator) } + unsafe { &*(self.inner as *mut core::Operator) } } } @@ -77,29 +69,24 @@ impl opendal_operator { pub unsafe extern "C" fn opendal_operator_free(ptr: *const opendal_operator) { unsafe { if !ptr.is_null() { - drop(Box::from_raw((*ptr).inner as *mut core::blocking::Operator)); + drop(Box::from_raw((*ptr).inner as *mut core::Operator)); drop(Box::from_raw(ptr as *mut opendal_operator)); } } } } -fn build_operator( - schema: &str, - map: HashMap, -) -> core::Result { +fn build_operator(schema: &str, map: HashMap) -> core::Result { core::init_default_registry(); - let op = core::Operator::via_iter(schema, map)?.layer(core::layers::RetryLayer::new()); - - build_blocking_operator(op) + Ok(core::Operator::via_iter(schema, map)?.layer(core::layers::RetryLayer::new())) } fn build_operator_with_layers( schema: &str, map: HashMap, layers: *const opendal_operator_layers, -) -> core::Result { +) -> core::Result { core::init_default_registry(); let mut op = core::Operator::via_iter(schema, map)?; @@ -107,14 +94,6 @@ fn build_operator_with_layers( op = unsafe { (*layers).apply(op) }; } - build_blocking_operator(op) -} - -fn build_blocking_operator(op: core::Operator) -> core::Result { - let runtime = - tokio::runtime::Handle::try_current().unwrap_or_else(|_| RUNTIME.handle().clone()); - let _guard = runtime.enter(); - let op = core::blocking::Operator::new(op)?; Ok(op) } @@ -130,7 +109,7 @@ fn parse_operator_options(options: *const opendal_operator_options) -> HashMap) -> opendal_result_operator_new { +fn new_operator_result(op: core::Result) -> opendal_result_operator_new { match op { Ok(op) => opendal_result_operator_new { op: Box::into_raw(Box::new(opendal_operator { @@ -145,6 +124,86 @@ fn new_operator_result(op: core::Result) -> opendal_re } } +unsafe fn parse_cstr<'a>(ptr: *const c_char, name: &str) -> &'a str { + assert!(!ptr.is_null()); + unsafe { std::ffi::CStr::from_ptr(ptr) } + .to_str() + .unwrap_or_else(|_| panic!("malformed {name}")) +} + +fn result_read(result: core::Result) -> opendal_result_read { + match result { + Ok(b) => opendal_result_read { + data: opendal_bytes::new(b), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_read { + data: opendal_bytes::empty(), + error: opendal_error::new(e), + }, + } +} + +fn result_stat(result: core::Result) -> opendal_result_stat { + match result { + Ok(m) => opendal_result_stat { + meta: Box::into_raw(Box::new(opendal_metadata::new(m))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_stat { + meta: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +fn result_error(result: core::Result<()>) -> *mut opendal_error { + match result { + Ok(_) => std::ptr::null_mut(), + Err(e) => opendal_error::new(e), + } +} + +unsafe fn parse_delete_options( + opts: *const opendal_delete_options, +) -> core::options::DeleteOptions { + if opts.is_null() { + return core::options::DeleteOptions::default(); + } + + let o = unsafe { &*opts }; + let version = if o.version.is_null() { + None + } else { + Some(unsafe { parse_cstr(o.version, "version") }.to_owned()) + }; + core::options::DeleteOptions { + version, + recursive: o.recursive, + } +} + +unsafe fn parse_list_options(opts: *const opendal_list_options) -> core::options::ListOptions { + if opts.is_null() { + return core::options::ListOptions::default(); + } + + let o = unsafe { &*opts }; + let limit = if o.limit == 0 { None } else { Some(o.limit) }; + let start_after = if o.start_after.is_null() { + None + } else { + Some(unsafe { parse_cstr(o.start_after, "start_after") }.to_owned()) + }; + core::options::ListOptions { + recursive: o.recursive, + limit, + start_after, + versions: o.versions, + deleted: o.deleted, + } +} + /// \brief Construct an operator based on `scheme` and `options` /// /// Uses an array of key-value pairs to initialize the operator based on provided `scheme` @@ -221,6 +280,422 @@ pub unsafe extern "C" fn opendal_operator_new_with_layers( new_operator_result(build_operator_with_layers(scheme, map, layers)) } +/// \brief Like `opendal_operator_write` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_write_with_cancel( + op: &opendal_operator, + path: *const c_char, + bytes: &opendal_bytes, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let bytes = core::Buffer::from(bytes); + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.write(&path, bytes).await.map(|_| ()) + })) +} + +/// \brief Like `opendal_operator_write_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_write_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + bytes: &opendal_bytes, + opts: *const opendal_write_options, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let bytes = core::Buffer::from(bytes); + let opts = if opts.is_null() { + core::options::WriteOptions::default() + } else { + unsafe { (&*opts).into() } + }; + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.write_options(&path, bytes, opts).await.map(|_| ()) + })) +} + +/// \brief Like `opendal_operator_read` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_read_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_read { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_read(cancel::block_on_cancelable_spawn(token, async move { + op.read(&path).await + })) +} + +/// \brief Like `opendal_operator_read_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_read_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_read_options, + token: *const opendal_cancel_token, +) -> opendal_result_read { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = if opts.is_null() { + core::options::ReadOptions::default() + } else { + unsafe { (&*opts).into() } + }; + let op = op.deref().clone(); + result_read(cancel::block_on_cancelable_spawn(token, async move { + op.read_options(&path, opts).await + })) +} + +/// \brief Like `opendal_operator_reader` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_reader_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_operator_reader { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn(token, async move { + let reader = op.reader(&path).await?; + opendal_reader::create_async(reader).await + }) { + Ok(reader) => opendal_result_operator_reader { + reader: Box::into_raw(Box::new(opendal_reader::from_async(reader))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_operator_reader { + reader: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_writer` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_writer_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_operator_writer { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn(token, async move { op.writer(&path).await }) { + Ok(writer) => opendal_result_operator_writer { + writer: Box::into_raw(Box::new(opendal_writer::new_async(writer))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_operator_writer { + writer: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_writer_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_writer_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_write_options, + token: *const opendal_cancel_token, +) -> opendal_result_operator_writer { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = if opts.is_null() { + core::options::WriteOptions::default() + } else { + unsafe { (&*opts).into() } + }; + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn( + token, + async move { op.writer_options(&path, opts).await }, + ) { + Ok(writer) => opendal_result_operator_writer { + writer: Box::into_raw(Box::new(opendal_writer::new_async(writer))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_operator_writer { + writer: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_delete` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_delete_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.delete(&path).await + })) +} + +/// \brief Like `opendal_operator_delete_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_delete_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_delete_options, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = unsafe { parse_delete_options(opts) }; + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.delete_options(&path, opts).await + })) +} + +/// \brief Like `opendal_operator_is_exist` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +#[cfg_attr(cbindgen, cbindgen::ignore)] +pub unsafe extern "C" fn opendal_operator_is_exist_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_is_exist { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn(token, async move { op.exists(&path).await }) { + Ok(e) => opendal_result_is_exist { + is_exist: e, + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_is_exist { + is_exist: false, + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_exists` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_exists_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_exists { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn(token, async move { op.exists(&path).await }) { + Ok(e) => opendal_result_exists { + exists: e, + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_exists { + exists: false, + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_stat` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_stat_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_stat { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_stat(cancel::block_on_cancelable_spawn(token, async move { + op.stat(&path).await + })) +} + +/// \brief Like `opendal_operator_stat_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_stat_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_stat_options, + token: *const opendal_cancel_token, +) -> opendal_result_stat { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = if opts.is_null() { + core::options::StatOptions::default() + } else { + unsafe { (&*opts).into() } + }; + let op = op.deref().clone(); + result_stat(cancel::block_on_cancelable_spawn(token, async move { + op.stat_options(&path, opts).await + })) +} + +/// \brief Like `opendal_operator_list` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_list_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> opendal_result_list { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn(token, async move { op.lister(&path).await }) { + Ok(lister) => opendal_result_list { + lister: Box::into_raw(Box::new(opendal_lister::new_async(lister))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_list { + lister: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_list_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_list_with_options_cancel( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_list_options, + token: *const opendal_cancel_token, +) -> opendal_result_list { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = unsafe { parse_list_options(opts) }; + let op = op.deref().clone(); + match cancel::block_on_cancelable_spawn( + token, + async move { op.lister_options(&path, opts).await }, + ) { + Ok(lister) => opendal_result_list { + lister: Box::into_raw(Box::new(opendal_lister::new_async(lister))), + error: std::ptr::null_mut(), + }, + Err(e) => opendal_result_list { + lister: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + +/// \brief Like `opendal_operator_create_dir` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( + op: &opendal_operator, + path: *const c_char, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.create_dir(&path).await + })) +} + +/// \brief Like `opendal_operator_rename` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_rename_with_cancel( + op: &opendal_operator, + src: *const c_char, + dest: *const c_char, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let src = unsafe { parse_cstr(src, "src") }.to_owned(); + let dest = unsafe { parse_cstr(dest, "dest") }.to_owned(); + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.rename(&src, &dest).await + })) +} + +/// \brief Like `opendal_operator_copy` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_copy_with_cancel( + op: &opendal_operator, + src: *const c_char, + dest: *const c_char, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + unsafe { opendal_operator_copy_with_options_cancel(op, src, dest, std::ptr::null(), token) } +} + +/// \brief Like `opendal_operator_copy_with` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_copy_with_options_cancel( + op: &opendal_operator, + src: *const c_char, + dest: *const c_char, + opts: *const opendal_copy_options, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let src = unsafe { parse_cstr(src, "src") }.to_owned(); + let dest = unsafe { parse_cstr(dest, "dest") }.to_owned(); + let copy_opts = if opts.is_null() { + core::options::CopyOptions::default() + } else { + unsafe { (&*opts).into() } + }; + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.copy_options(&src, &dest, copy_opts).await.map(|_| ()) + })) +} + +/// \brief Like `opendal_operator_check` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_check_with_cancel( + op: &opendal_operator, + token: *const opendal_cancel_token, +) -> *mut opendal_error { + let op = op.deref().clone(); + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.check().await + })) +} + /// \brief Blocking write raw bytes to `path`. /// /// Write the `bytes` into the `path` blocking by `op_ptr`. @@ -271,14 +746,7 @@ pub unsafe extern "C" fn opendal_operator_write( path: *const c_char, bytes: &opendal_bytes, ) -> *mut opendal_error { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().write(path, bytes) { - Ok(_) => std::ptr::null_mut(), - Err(e) => opendal_error::new(e), - } + unsafe { opendal_operator_write_with_cancel(op, path, bytes, std::ptr::null()) } } /// \brief Blocking write raw bytes to `path` with options. @@ -289,19 +757,7 @@ pub unsafe extern "C" fn opendal_operator_write_with( bytes: &opendal_bytes, opts: *const opendal_write_options, ) -> *mut opendal_error { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let opts = if opts.is_null() { - core::options::WriteOptions::default() - } else { - (&*opts).into() - }; - match op.deref().write_options(path, bytes, opts) { - Ok(_) => std::ptr::null_mut(), - Err(e) => opendal_error::new(e), - } + unsafe { opendal_operator_write_with_options_cancel(op, path, bytes, opts, std::ptr::null()) } } /// \brief Blocking read the data from `path`. @@ -348,20 +804,7 @@ pub unsafe extern "C" fn opendal_operator_read( op: &opendal_operator, path: *const c_char, ) -> opendal_result_read { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().read(path) { - Ok(b) => opendal_result_read { - data: opendal_bytes::new(b), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_read { - data: opendal_bytes::empty(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_read_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking read the data from `path` with options. @@ -399,25 +842,7 @@ pub unsafe extern "C" fn opendal_operator_read_with( path: *const c_char, opts: *const opendal_read_options, ) -> opendal_result_read { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let opts = if opts.is_null() { - core::options::ReadOptions::default() - } else { - (&*opts).into() - }; - match op.deref().read_options(path, opts) { - Ok(b) => opendal_result_read { - data: opendal_bytes::new(b), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_read { - data: opendal_bytes::empty(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_read_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking read the data from `path`. @@ -461,30 +886,7 @@ pub unsafe extern "C" fn opendal_operator_reader( op: &opendal_operator, path: *const c_char, ) -> opendal_result_operator_reader { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let reader = match op.deref().reader(path) { - Ok(reader) => reader, - Err(err) => { - return opendal_result_operator_reader { - reader: std::ptr::null_mut(), - error: opendal_error::new(err), - }; - } - }; - - match reader.into_std_read(..) { - Ok(reader) => opendal_result_operator_reader { - reader: Box::into_raw(Box::new(opendal_reader::new(reader))), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_operator_reader { - reader: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_reader_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking create a writer for the specified path. @@ -528,24 +930,7 @@ pub unsafe extern "C" fn opendal_operator_writer( op: &opendal_operator, path: *const c_char, ) -> opendal_result_operator_writer { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let writer = match op.deref().writer(path) { - Ok(writer) => writer, - Err(err) => { - return opendal_result_operator_writer { - writer: std::ptr::null_mut(), - error: opendal_error::new(err), - }; - } - }; - - opendal_result_operator_writer { - writer: Box::into_raw(Box::new(opendal_writer::new(writer))), - error: std::ptr::null_mut(), - } + unsafe { opendal_operator_writer_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking create a writer for the specified path with options. @@ -555,29 +940,7 @@ pub unsafe extern "C" fn opendal_operator_writer_with( path: *const c_char, opts: *const opendal_write_options, ) -> opendal_result_operator_writer { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let opts = if opts.is_null() { - core::options::WriteOptions::default() - } else { - (&*opts).into() - }; - let writer = match op.deref().writer_options(path, opts) { - Ok(writer) => writer, - Err(err) => { - return opendal_result_operator_writer { - writer: std::ptr::null_mut(), - error: opendal_error::new(err), - }; - } - }; - - opendal_result_operator_writer { - writer: Box::into_raw(Box::new(opendal_writer::new(writer))), - error: std::ptr::null_mut(), - } + unsafe { opendal_operator_writer_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking delete the object in `path`. @@ -625,14 +988,7 @@ pub unsafe extern "C" fn opendal_operator_delete( op: &opendal_operator, path: *const c_char, ) -> *mut opendal_error { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().delete(path) { - Ok(_) => std::ptr::null_mut(), - Err(e) => opendal_error::new(e), - } + unsafe { opendal_operator_delete_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking delete the object in `path` with options. @@ -661,33 +1017,7 @@ pub unsafe extern "C" fn opendal_operator_delete_with( path: *const c_char, opts: *const opendal_delete_options, ) -> *mut opendal_error { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let delete_opts = if opts.is_null() { - core::options::DeleteOptions::default() - } else { - let o = &*opts; - let version = if o.version.is_null() { - None - } else { - Some( - std::ffi::CStr::from_ptr(o.version) - .to_str() - .expect("malformed version") - .to_owned(), - ) - }; - core::options::DeleteOptions { - version, - recursive: o.recursive, - } - }; - match op.deref().delete_options(path, delete_opts) { - Ok(_) => std::ptr::null_mut(), - Err(e) => opendal_error::new(e), - } + unsafe { opendal_operator_delete_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Check whether the path exists. @@ -733,20 +1063,7 @@ pub unsafe extern "C" fn opendal_operator_is_exist( op: &opendal_operator, path: *const c_char, ) -> opendal_result_is_exist { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().exists(path) { - Ok(e) => opendal_result_is_exist { - is_exist: e, - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_is_exist { - is_exist: false, - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_is_exist_with_cancel(op, path, std::ptr::null()) } } /// \brief Check whether the path exists. @@ -791,20 +1108,7 @@ pub unsafe extern "C" fn opendal_operator_exists( op: &opendal_operator, path: *const c_char, ) -> opendal_result_exists { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().exists(path) { - Ok(e) => opendal_result_exists { - exists: e, - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_exists { - exists: false, - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_exists_with_cancel(op, path, std::ptr::null()) } } /// \brief Stat the path, return its metadata. @@ -848,20 +1152,7 @@ pub unsafe extern "C" fn opendal_operator_stat( op: &opendal_operator, path: *const c_char, ) -> opendal_result_stat { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().stat(path) { - Ok(m) => opendal_result_stat { - meta: Box::into_raw(Box::new(opendal_metadata::new(m))), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_stat { - meta: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_stat_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking stat the object in `path` with options. @@ -892,25 +1183,7 @@ pub unsafe extern "C" fn opendal_operator_stat_with( path: *const c_char, opts: *const opendal_stat_options, ) -> opendal_result_stat { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let opts = if opts.is_null() { - core::options::StatOptions::default() - } else { - (&*opts).into() - }; - match op.deref().stat_options(path, opts) { - Ok(m) => opendal_result_stat { - meta: Box::into_raw(Box::new(opendal_metadata::new(m))), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_stat { - meta: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_stat_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking list the objects in `path`. @@ -966,20 +1239,7 @@ pub unsafe extern "C" fn opendal_operator_list( op: &opendal_operator, path: *const c_char, ) -> opendal_result_list { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().lister(path) { - Ok(lister) => opendal_result_list { - lister: Box::into_raw(Box::new(opendal_lister::new(lister))), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_list { - lister: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_list_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking list the objects in `path` with options. @@ -1009,43 +1269,7 @@ pub unsafe extern "C" fn opendal_operator_list_with( path: *const c_char, opts: *const opendal_list_options, ) -> opendal_result_list { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - let list_opts = if opts.is_null() { - core::options::ListOptions::default() - } else { - let o = &*opts; - let limit = if o.limit == 0 { None } else { Some(o.limit) }; - let start_after = if o.start_after.is_null() { - None - } else { - Some( - std::ffi::CStr::from_ptr(o.start_after) - .to_str() - .expect("malformed start_after") - .to_owned(), - ) - }; - core::options::ListOptions { - recursive: o.recursive, - limit, - start_after, - versions: o.versions, - deleted: o.deleted, - } - }; - match op.deref().lister_options(path, list_opts) { - Ok(lister) => opendal_result_list { - lister: Box::into_raw(Box::new(opendal_lister::new(lister))), - error: std::ptr::null_mut(), - }, - Err(e) => opendal_result_list { - lister: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_list_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking create the directory in `path`. @@ -1086,15 +1310,7 @@ pub unsafe extern "C" fn opendal_operator_create_dir( op: &opendal_operator, path: *const c_char, ) -> *mut opendal_error { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - if let Err(err) = op.deref().create_dir(path) { - opendal_error::new(err) - } else { - std::ptr::null_mut() - } + unsafe { opendal_operator_create_dir_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking rename the object in `path`. @@ -1144,19 +1360,7 @@ pub unsafe extern "C" fn opendal_operator_rename( src: *const c_char, dest: *const c_char, ) -> *mut opendal_error { - assert!(!src.is_null()); - assert!(!dest.is_null()); - let src = std::ffi::CStr::from_ptr(src) - .to_str() - .expect("malformed src"); - let dest = std::ffi::CStr::from_ptr(dest) - .to_str() - .expect("malformed dest"); - if let Err(err) = op.deref().rename(src, dest) { - opendal_error::new(err) - } else { - std::ptr::null_mut() - } + unsafe { opendal_operator_rename_with_cancel(op, src, dest, std::ptr::null()) } } /// \brief Blocking copy the object in `path`. @@ -1206,19 +1410,7 @@ pub unsafe extern "C" fn opendal_operator_copy( src: *const c_char, dest: *const c_char, ) -> *mut opendal_error { - assert!(!src.is_null()); - assert!(!dest.is_null()); - let src = std::ffi::CStr::from_ptr(src) - .to_str() - .expect("malformed src"); - let dest = std::ffi::CStr::from_ptr(dest) - .to_str() - .expect("malformed dest"); - if let Err(err) = op.deref().copy(src, dest) { - opendal_error::new(err) - } else { - std::ptr::null_mut() - } + unsafe { opendal_operator_copy_with_cancel(op, src, dest, std::ptr::null()) } } /// \brief Blocking copy the object in `path` with options. @@ -1279,31 +1471,10 @@ pub unsafe extern "C" fn opendal_operator_copy_with( dest: *const c_char, opts: *const opendal_copy_options, ) -> *mut opendal_error { - assert!(!src.is_null()); - assert!(!dest.is_null()); - let src = std::ffi::CStr::from_ptr(src) - .to_str() - .expect("malformed src"); - let dest = std::ffi::CStr::from_ptr(dest) - .to_str() - .expect("malformed dest"); - let copy_opts = if opts.is_null() { - core::options::CopyOptions::default() - } else { - core::options::CopyOptions::from(&*opts) - }; - if let Err(err) = op.deref().copy_options(src, dest, copy_opts) { - opendal_error::new(err) - } else { - std::ptr::null_mut() - } + unsafe { opendal_operator_copy_with_options_cancel(op, src, dest, opts, std::ptr::null()) } } #[no_mangle] pub unsafe extern "C" fn opendal_operator_check(op: &opendal_operator) -> *mut opendal_error { - if let Err(err) = op.deref().check() { - opendal_error::new(err) - } else { - std::ptr::null_mut() - } + unsafe { opendal_operator_check_with_cancel(op, std::ptr::null()) } } diff --git a/bindings/c/src/presign.rs b/bindings/c/src/presign.rs index 705f3334a8b9..21058b559939 100644 --- a/bindings/c/src/presign.rs +++ b/bindings/c/src/presign.rs @@ -18,9 +18,12 @@ use std::ffi::{c_char, CStr, CString}; use std::time::Duration; +use ::opendal as core; use opendal::raw::PresignedRequest as ocorePresignedRequest; +use crate::cancel; use crate::error::opendal_error; +use crate::opendal_cancel_token; use crate::operator::opendal_operator; /// \brief The key-value pair for the headers of the presigned request. @@ -81,6 +84,25 @@ impl opendal_presigned_request_inner { } } +fn result_presign(result: core::Result) -> opendal_result_presign { + match result { + Ok(req) => { + let inner = Box::new(opendal_presigned_request_inner::new(req)); + let presigned_req = Box::new(opendal_presigned_request { + inner: Box::into_raw(inner), + }); + opendal_result_presign { + req: Box::into_raw(presigned_req), + error: std::ptr::null_mut(), + } + } + Err(e) => opendal_result_presign { + req: std::ptr::null_mut(), + error: opendal_error::new(e), + }, + } +} + /// \brief The underlying presigned request, which contains the HTTP method, URI, and headers. /// This is an opaque struct, please use the accessor functions to get the fields. #[repr(C)] @@ -97,66 +119,112 @@ pub struct opendal_result_presign { pub error: *mut opendal_error, } -/// \brief Presign a read operation. +/// \brief Like `opendal_operator_presign_read` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_presign_read( +pub unsafe extern "C" fn opendal_operator_presign_read_with_cancel( op: &opendal_operator, path: *const c_char, expire_secs: u64, + token: *const opendal_cancel_token, ) -> opendal_result_presign { assert!(!path.is_null()); - - let op = op.deref(); - let path = CStr::from_ptr(path).to_str().expect("malformed path"); + let path = CStr::from_ptr(path) + .to_str() + .expect("malformed path") + .to_owned(); let duration = Duration::from_secs(expire_secs); + let op = op.deref().clone(); + result_presign(cancel::block_on_cancelable_spawn(token, async move { + op.presign_read(&path, duration).await + })) +} - match op.presign_read(path, duration) { - Ok(req) => { - let inner = Box::new(opendal_presigned_request_inner::new(req)); - let presigned_req = Box::new(opendal_presigned_request { - inner: Box::into_raw(inner), - }); - opendal_result_presign { - req: Box::into_raw(presigned_req), - error: std::ptr::null_mut(), - } - } - Err(e) => opendal_result_presign { - req: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } +/// \brief Like `opendal_operator_presign_write` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_write_with_cancel( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, + token: *const opendal_cancel_token, +) -> opendal_result_presign { + assert!(!path.is_null()); + let path = CStr::from_ptr(path) + .to_str() + .expect("malformed path") + .to_owned(); + let duration = Duration::from_secs(expire_secs); + let op = op.deref().clone(); + result_presign(cancel::block_on_cancelable_spawn(token, async move { + op.presign_write(&path, duration).await + })) } -/// \brief Presign a write operation. +/// \brief Like `opendal_operator_presign_delete` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_presign_write( +pub unsafe extern "C" fn opendal_operator_presign_delete_with_cancel( op: &opendal_operator, path: *const c_char, expire_secs: u64, + token: *const opendal_cancel_token, ) -> opendal_result_presign { assert!(!path.is_null()); + let path = CStr::from_ptr(path) + .to_str() + .expect("malformed path") + .to_owned(); + let duration = Duration::from_secs(expire_secs); + let op = op.deref().clone(); + result_presign(cancel::block_on_cancelable_spawn(token, async move { + op.presign_delete(&path, duration).await + })) +} - let op = op.deref(); - let path = CStr::from_ptr(path).to_str().expect("malformed path"); +/// \brief Like `opendal_operator_presign_stat` with cooperative cancellation. +/// +/// Pass NULL for `token` to block until completion. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_stat_with_cancel( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, + token: *const opendal_cancel_token, +) -> opendal_result_presign { + assert!(!path.is_null()); + let path = CStr::from_ptr(path) + .to_str() + .expect("malformed path") + .to_owned(); let duration = Duration::from_secs(expire_secs); + let op = op.deref().clone(); + result_presign(cancel::block_on_cancelable_spawn(token, async move { + op.presign_stat(&path, duration).await + })) +} - match op.presign_write(path, duration) { - Ok(req) => { - let inner = Box::new(opendal_presigned_request_inner::new(req)); - let presigned_req = Box::new(opendal_presigned_request { - inner: Box::into_raw(inner), - }); - opendal_result_presign { - req: Box::into_raw(presigned_req), - error: std::ptr::null_mut(), - } - } - Err(e) => opendal_result_presign { - req: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } +/// \brief Presign a read operation. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_read( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, +) -> opendal_result_presign { + unsafe { opendal_operator_presign_read_with_cancel(op, path, expire_secs, std::ptr::null()) } +} + +/// \brief Presign a write operation. +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_write( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, +) -> opendal_result_presign { + unsafe { opendal_operator_presign_write_with_cancel(op, path, expire_secs, std::ptr::null()) } } /// \brief Presign a delete operation. @@ -166,27 +234,7 @@ pub unsafe extern "C" fn opendal_operator_presign_delete( path: *const c_char, expire_secs: u64, ) -> opendal_result_presign { - assert!(!path.is_null()); - - let op = op.deref(); - let path = CStr::from_ptr(path).to_str().expect("malformed path"); - let duration = Duration::from_secs(expire_secs); - match op.presign_delete(path, duration) { - Ok(req) => { - let inner = Box::new(opendal_presigned_request_inner::new(req)); - let presigned_req = Box::new(opendal_presigned_request { - inner: Box::into_raw(inner), - }); - opendal_result_presign { - req: Box::into_raw(presigned_req), - error: std::ptr::null_mut(), - } - } - Err(e) => opendal_result_presign { - req: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_presign_delete_with_cancel(op, path, expire_secs, std::ptr::null()) } } /// \brief Presign a stat operation. @@ -196,28 +244,7 @@ pub unsafe extern "C" fn opendal_operator_presign_stat( path: *const c_char, expire_secs: u64, ) -> opendal_result_presign { - assert!(!path.is_null()); - - let op = op.deref(); - let path = CStr::from_ptr(path).to_str().expect("malformed path"); - let duration = Duration::from_secs(expire_secs); - - match op.presign_stat(path, duration) { - Ok(req) => { - let inner = Box::new(opendal_presigned_request_inner::new(req)); - let presigned_req = Box::new(opendal_presigned_request { - inner: Box::into_raw(inner), - }); - opendal_result_presign { - req: Box::into_raw(presigned_req), - error: std::ptr::null_mut(), - } - } - Err(e) => opendal_result_presign { - req: std::ptr::null_mut(), - error: opendal_error::new(e), - }, - } + unsafe { opendal_operator_presign_stat_with_cancel(op, path, expire_secs, std::ptr::null()) } } /// Get the method of the presigned request. diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index 1335de0edf41..33cd34d13e72 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -16,9 +16,11 @@ // under the License. use std::ffi::c_void; -use std::io::{Read, Seek, SeekFrom}; +use std::io::SeekFrom; use ::opendal as core; +use futures_util::AsyncReadExt; +use futures_util::AsyncSeekExt; use crate::result::opendal_result_reader_seek; @@ -31,100 +33,159 @@ pub const OPENDAL_SEEK_END: i32 = 2; /// \brief The result type returned by opendal's reader operation. /// /// \note The opendal_reader actually owns a pointer to -/// a opendal::BlockingReader, which is inside the Rust core code. +/// a opendal::FuturesAsyncReader, which is inside the Rust core code. #[repr(C)] pub struct opendal_reader { - /// The pointer to the opendal::StdReader in the Rust code. + /// The pointer to the opendal::FuturesAsyncReader in the Rust code. /// Only touch this on judging whether it is NULL. inner: *mut c_void, } +pub(crate) struct AsyncReader { + reader: core::FuturesAsyncReader, +} + impl opendal_reader { - fn deref_mut(&mut self) -> &mut core::blocking::StdReader { + fn deref_mut(&mut self) -> &mut AsyncReader { // Safety: the inner should never be null once constructed // The use-after-free is undefined behavior - unsafe { &mut *(self.inner as *mut core::blocking::StdReader) } + unsafe { &mut *(self.inner as *mut AsyncReader) } } } impl opendal_reader { - pub(crate) fn new(reader: core::blocking::StdReader) -> Self { + pub(crate) async fn create_async(reader: core::Reader) -> core::Result { + let reader = reader.into_futures_async_read(..).await?; + Ok(AsyncReader { reader }) + } + + pub(crate) fn from_async(reader: AsyncReader) -> Self { Self { inner: Box::into_raw(Box::new(reader)) as _, } } - /// \brief Read data from the reader. - #[no_mangle] - pub unsafe extern "C" fn opendal_reader_read( - &mut self, - buf: *mut u8, - len: usize, - ) -> opendal_result_reader_read { - assert!(!buf.is_null()); - let buf = std::slice::from_raw_parts_mut(buf, len); - match self.deref_mut().read(buf) { + async fn read_async_inner(reader: &mut AsyncReader, buf: &mut [u8]) -> core::Result { + reader + .reader + .read(buf) + .await + .map_err(|err| core::Error::new(core::ErrorKind::Unexpected, err.to_string())) + } + + fn result_read(result: core::Result) -> opendal_result_reader_read { + match result { Ok(n) => opendal_result_reader_read { size: n, error: std::ptr::null_mut(), }, Err(e) => opendal_result_reader_read { size: 0, - error: opendal_error::new( - core::Error::new(core::ErrorKind::Unexpected, "read failed from reader") - .set_source(e), - ), + error: opendal_error::new(e), }, } } - /// \brief Seek to an offset, in bytes, in a stream. + /// \brief Like `opendal_reader_read` with cooperative cancellation. + /// + /// Pass NULL for `token` to block until completion. #[no_mangle] - pub unsafe extern "C" fn opendal_reader_seek( + pub unsafe extern "C" fn opendal_reader_read_with_cancel( + &mut self, + buf: *mut u8, + len: usize, + token: *const opendal_cancel_token, + ) -> opendal_result_reader_read { + assert!(!buf.is_null()); + let buf = std::slice::from_raw_parts_mut(buf, len); + let reader = self.deref_mut(); + Self::result_read(cancel::block_on_cancelable(token, async move { + Self::read_async_inner(reader, buf).await + })) + } + + /// \brief Read data from the reader. + #[no_mangle] + pub unsafe extern "C" fn opendal_reader_read( + &mut self, + buf: *mut u8, + len: usize, + ) -> opendal_result_reader_read { + unsafe { Self::opendal_reader_read_with_cancel(self, buf, len, std::ptr::null()) } + } + + /// \brief Like `opendal_reader_seek` with cooperative cancellation. + /// + /// Pass NULL for `token` to block until completion. + #[no_mangle] + pub unsafe extern "C" fn opendal_reader_seek_with_cancel( &mut self, offset: i64, whence: i32, + token: *const opendal_cancel_token, ) -> opendal_result_reader_seek { - let pos = match whence { - _x @ OPENDAL_SEEK_SET => SeekFrom::Start(offset as u64), - _x @ OPENDAL_SEEK_CUR => SeekFrom::Current(offset), - _x @ OPENDAL_SEEK_END => SeekFrom::End(offset), - _ => { - return opendal_result_reader_seek { - pos: 0, - error: opendal_error::new(core::Error::new( - core::ErrorKind::Unexpected, - "undefined whence", - )), - }; - } - }; - - match self.deref_mut().seek(pos) { + let reader = self.deref_mut(); + match cancel::block_on_cancelable(token, async move { + seek_async_reader(reader, offset, whence).await + }) { Ok(pos) => opendal_result_reader_seek { pos, error: std::ptr::null_mut(), }, Err(e) => opendal_result_reader_seek { pos: 0, - error: opendal_error::new( - core::Error::new(core::ErrorKind::Unexpected, "seek failed from reader") - .set_source(e), - ), + error: opendal_error::new(e), }, } } + /// \brief Seek to an offset, in bytes, in a stream. + #[no_mangle] + pub unsafe extern "C" fn opendal_reader_seek( + &mut self, + offset: i64, + whence: i32, + ) -> opendal_result_reader_seek { + unsafe { Self::opendal_reader_seek_with_cancel(self, offset, whence, std::ptr::null()) } + } + /// \brief Frees the heap memory used by the opendal_reader. #[no_mangle] pub unsafe extern "C" fn opendal_reader_free(ptr: *mut opendal_reader) { unsafe { if !ptr.is_null() { - drop(Box::from_raw( - (*ptr).inner as *mut core::blocking::StdReader, - )); + drop(Box::from_raw((*ptr).inner as *mut AsyncReader)); drop(Box::from_raw(ptr)); } } } } + +async fn seek_async_reader( + reader: &mut AsyncReader, + offset: i64, + whence: i32, +) -> core::Result { + let pos = match whence { + _x @ OPENDAL_SEEK_SET => SeekFrom::Start(offset.try_into().map_err(|_| { + core::Error::new( + core::ErrorKind::Unexpected, + "reader seek position is negative", + ) + })?), + _x @ OPENDAL_SEEK_CUR => SeekFrom::Current(offset), + _x @ OPENDAL_SEEK_END => SeekFrom::End(offset), + _ => { + return Err(core::Error::new( + core::ErrorKind::Unexpected, + "undefined whence", + )); + } + }; + + reader + .reader + .seek(pos) + .await + .map_err(|err| core::Error::new(core::ErrorKind::Unexpected, err.to_string())) +} diff --git a/bindings/c/src/runtime.rs b/bindings/c/src/runtime.rs new file mode 100644 index 000000000000..c6b0cc3bdb95 --- /dev/null +++ b/bindings/c/src/runtime.rs @@ -0,0 +1,31 @@ +// 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::sync::LazyLock; + +/// Shared Tokio runtime used to bridge blocking C callers to async OpenDAL APIs. +/// +/// Every blocking C call uses `RUNTIME.block_on(...)` on the calling thread. +/// Calling any blocking OpenDAL C function from inside an existing Tokio +/// runtime thread will panic, because `block_on` cannot be nested. This is +/// the same constraint as the old `blocking::Operator` API. +pub(crate) static RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to build tokio runtime") +}); diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs index 2b0eafa05a8c..c63d4ac65524 100644 --- a/bindings/c/src/writer.rs +++ b/bindings/c/src/writer.rs @@ -31,64 +31,93 @@ pub struct opendal_writer { } impl opendal_writer { - fn deref_mut(&mut self) -> &mut core::blocking::Writer { + fn deref_mut(&mut self) -> &mut core::Writer { // Safety: the inner should never be null once constructed // The use-after-free is undefined behavior - unsafe { &mut *(self.inner as *mut core::blocking::Writer) } + unsafe { &mut *(self.inner as *mut core::Writer) } } } impl opendal_writer { - pub(crate) fn new(writer: core::blocking::Writer) -> Self { + pub(crate) fn new_async(writer: core::Writer) -> Self { Self { inner: Box::into_raw(Box::new(writer)) as _, } } - /// \brief Write data to the writer. - #[no_mangle] - pub unsafe extern "C" fn opendal_writer_write( - &mut self, - bytes: &opendal_bytes, - ) -> opendal_result_writer_write { - let size = bytes.len; - match self.deref_mut().write(bytes) { - Ok(()) => opendal_result_writer_write { + fn result_write(result: core::Result) -> opendal_result_writer_write { + match result { + Ok(size) => opendal_result_writer_write { size, error: std::ptr::null_mut(), }, Err(e) => opendal_result_writer_write { size: 0, - error: opendal_error::new( - core::Error::new(core::ErrorKind::Unexpected, "write failed from writer") - .set_source(e), - ), + error: opendal_error::new(e), }, } } - /// \brief Close the writer and make sure all data have been stored. + /// \brief Like `opendal_writer_write` with cooperative cancellation. + /// + /// Pass NULL for `token` to block until completion. #[no_mangle] - pub unsafe extern "C" fn opendal_writer_close(ptr: *mut opendal_writer) -> *mut opendal_error { + pub unsafe extern "C" fn opendal_writer_write_with_cancel( + &mut self, + bytes: &opendal_bytes, + token: *const opendal_cancel_token, + ) -> opendal_result_writer_write { + let size = bytes.len; + let bytes = core::Buffer::from(bytes); + let writer = self.deref_mut(); + Self::result_write(cancel::block_on_cancelable(token, async move { + writer.write(bytes).await?; + Ok(size) + })) + } + + /// \brief Write data to the writer. + #[no_mangle] + pub unsafe extern "C" fn opendal_writer_write( + &mut self, + bytes: &opendal_bytes, + ) -> opendal_result_writer_write { + unsafe { Self::opendal_writer_write_with_cancel(self, bytes, std::ptr::null()) } + } + + /// \brief Like `opendal_writer_close` with cooperative cancellation. + /// + /// Pass NULL for `token` to block until completion. + #[no_mangle] + pub unsafe extern "C" fn opendal_writer_close_with_cancel( + ptr: *mut opendal_writer, + token: *const opendal_cancel_token, + ) -> *mut opendal_error { unsafe { if !ptr.is_null() { - if let Err(e) = (*ptr).deref_mut().close() { - return opendal_error::new( - core::Error::new(core::ErrorKind::Unexpected, "close writer failed") - .set_source(e), - ); + let writer = (*ptr).deref_mut(); + if let Err(e) = + cancel::block_on_cancelable(token, async move { writer.close().await }) + { + return opendal_error::new(e); } } std::ptr::null_mut() } } + /// \brief Close the writer and make sure all data have been stored. + #[no_mangle] + pub unsafe extern "C" fn opendal_writer_close(ptr: *mut opendal_writer) -> *mut opendal_error { + unsafe { Self::opendal_writer_close_with_cancel(ptr, std::ptr::null()) } + } + /// \brief Frees the heap memory used by the opendal_writer. #[no_mangle] pub unsafe extern "C" fn opendal_writer_free(ptr: *mut opendal_writer) { unsafe { if !ptr.is_null() { - drop(Box::from_raw((*ptr).inner as *mut core::blocking::Writer)); + drop(Box::from_raw((*ptr).inner as *mut core::Writer)); drop(Box::from_raw(ptr)); } } diff --git a/bindings/go/README.md b/bindings/go/README.md index ecdcb2f86e75..1f3fb6c1fdbd 100644 --- a/bindings/go/README.md +++ b/bindings/go/README.md @@ -19,6 +19,7 @@ opendal-go requires **libffi** to be installed. package main import ( + "context" "fmt" "github.com/apache/opendal-go-services/memory" opendal "github.com/apache/opendal/bindings/go" @@ -32,21 +33,23 @@ func main() { } defer op.Close() + ctx := context.Background() + // Write data to a file named "test" - err = op.Write("test", []byte("Hello opendal go binding!")) + err = op.Write(ctx, "test", []byte("Hello opendal go binding!")) if err != nil { panic(err) } // Read data from the file "test" - data, err := op.Read("test") + data, err := op.Read(ctx, "test") if err != nil { panic(err) } fmt.Printf("Read content: %s\n", data) // List all entries under the root directory "/" - lister, err := op.List("/") + lister, err := op.List(ctx, "/") if err != nil { panic(err) } @@ -60,7 +63,7 @@ func main() { _ = entry.Name() // Get metadata for the current entry - meta, _ := op.Stat(entry.Path()) + meta, _ := op.Stat(ctx, entry.Path()) // Print file size fmt.Printf("Size: %d bytes\n", meta.ContentLength()) @@ -78,16 +81,41 @@ func main() { } // Copy a file - op.Copy("test", "test_copy") + op.Copy(ctx, "test", "test_copy") // Rename a file - op.Rename("test", "test_rename") + op.Rename(ctx, "test", "test_rename") // Delete a file - op.Delete("test_rename") + op.Delete(ctx, "test_rename") } ``` +## Context Cancellation + +Operator methods take a `context.Context` as their first argument. For the +streaming handles, the context is bound when the handle is created +(`op.Reader(ctx, path)`, `op.Writer(ctx, path)`, `op.List(ctx, path)`) and +governs the subsequent `Read`/`Seek`/`Write`/`Close`/`Next` calls so those +types keep their standard `io` interface signatures. + +When the Go context is canceled or its deadline is exceeded, the binding +signals OpenDAL's native cancel token and then blocks until the native call +actually returns before reporting `context.Canceled` or +`context.DeadlineExceeded`. Waiting for the native call to finish guarantees the +underlying buffers and handles are no longer in use by native code once the call +returns. + +After a cancellation the streaming handles (`Reader`, `Writer`, `Lister`) are +still alive and can be closed without leaking resources, but their internal +stream state is unspecified (e.g. the read position may have advanced +partially). Callers should **discard** a handle that had an operation cancelled +and open a new one rather than attempting to resume from the same handle. + +`Writer.Close` may be retried after a cancelled close, but success is not +guaranteed because the underlying `core::Writer::close` future is not +documented as resumable. + ## Run Tests ### Behavior Tests diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go new file mode 100644 index 000000000000..b194c65f87de --- /dev/null +++ b/bindings/go/context_test.go @@ -0,0 +1,201 @@ +/* + * 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. + */ + +package opendal + +import ( + "context" + "errors" + "testing" + "time" +) + +func newCanceledContext() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx +} + +func TestOperatorContextFirstMethodsReturnCanceledContext(t *testing.T) { + ctx := newCanceledContext() + op := &Operator{} + + if _, err := op.Read(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Read() error = %v, want context.Canceled", err) + } + if err := op.Write(ctx, "path", []byte("data")); !errors.Is(err, context.Canceled) { + t.Fatalf("Write() error = %v, want context.Canceled", err) + } + if _, err := op.Stat(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Stat() error = %v, want context.Canceled", err) + } + if _, err := op.IsExist(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("IsExist() error = %v, want context.Canceled", err) + } + if err := op.Delete(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Delete() error = %v, want context.Canceled", err) + } + if _, err := op.List(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("List() error = %v, want context.Canceled", err) + } + if err := op.Check(ctx); !errors.Is(err, context.Canceled) { + t.Fatalf("Check() error = %v, want context.Canceled", err) + } + if err := op.CreateDir(ctx, "path/"); !errors.Is(err, context.Canceled) { + t.Fatalf("CreateDir() error = %v, want context.Canceled", err) + } + if _, err := op.Reader(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Reader() error = %v, want context.Canceled", err) + } + if _, err := op.Writer(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Writer() error = %v, want context.Canceled", err) + } + if err := op.Copy(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { + t.Fatalf("Copy() error = %v, want context.Canceled", err) + } + if err := op.Rename(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { + t.Fatalf("Rename() error = %v, want context.Canceled", err) + } + if _, err := op.PresignRead(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignRead() error = %v, want context.Canceled", err) + } + if _, err := op.PresignWrite(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignWrite() error = %v, want context.Canceled", err) + } + if _, err := op.PresignDelete(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignDelete() error = %v, want context.Canceled", err) + } + if _, err := op.PresignStat(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignStat() error = %v, want context.Canceled", err) + } +} + +func TestWriterCloseWithContextPreCancelledPreservesHandle(t *testing.T) { + inner := &opendalWriter{} + w := &Writer{inner: inner, cancelCtx: newCanceledContext()} + + if err := w.Close(); !errors.Is(err, context.Canceled) { + t.Fatalf("Close() error = %v, want context.Canceled", err) + } + if w.inner != inner { + t.Fatal("pre-cancelled close should not release writer handle") + } +} + +func TestWriterCloseIsIdempotent(t *testing.T) { + w := &Writer{} + + if err := w.Close(); err != nil { + t.Fatalf("first Close() = %v, want nil", err) + } + if err := w.Close(); err != nil { + t.Fatalf("second Close() = %v, want nil", err) + } +} + +func TestWriterFreeIsIdempotent(t *testing.T) { + w := &Writer{inner: &opendalWriter{}} + + w.mu.Lock() + w.inner = nil + w.mu.Unlock() + + w.free() + w.free() +} + +func TestWriterCloseShouldReleaseAfterClose(t *testing.T) { + cases := []struct { + name string + err error + want bool + }{ + {name: "success", err: nil, want: true}, + {name: "native error", err: errors.New("close failed"), want: true}, + {name: "canceled", err: context.Canceled, want: false}, + {name: "deadline exceeded", err: context.DeadlineExceeded, want: false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := shouldReleaseWriterAfterClose(tc.err); got != tc.want { + t.Fatalf("shouldReleaseWriterAfterClose(%v) = %v, want %v", tc.err, got, tc.want) + } + }) + } +} + +func TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { + inner := &opendalWriter{} + w := &Writer{inner: inner, cancelCtx: newCanceledContext()} + + if err := w.Close(); !errors.Is(err, context.Canceled) { + t.Fatalf("Close() error = %v, want context.Canceled", err) + } + if w.inner != inner { + t.Fatal("pre-cancelled close should preserve writer handle for deferred close") + } +} + +// TestInFlightCancellationBlocksUntilNativeReturns verifies the blocking +// cancellation contract: the binding must not return to the caller before the +// native FFI call finishes. +// +// This test only exercises the pre-cancel fast path (ctx already done before +// the FFI call starts) because a true in-flight test requires a live backend +// (e.g. OPENDAL_TEST=s3) to produce a long-running native call that can be +// interrupted mid-way. Running the behavior suite with a timeout context and +// asserting the goroutine count does not increase is a recommended integration +// check before release. +func TestPreCancelReturnsCanceled(t *testing.T) { + ctx := newCanceledContext() + + op := &Operator{} + if _, err := op.Read(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Read with pre-cancelled ctx = %v, want context.Canceled", err) + } + if err := op.Write(ctx, "path", []byte("x")); !errors.Is(err, context.Canceled) { + t.Fatalf("Write with pre-cancelled ctx = %v, want context.Canceled", err) + } +} + +func TestIOHandleMethodsUseBoundCanceledContext(t *testing.T) { + ctx := newCanceledContext() + + reader := &Reader{cancelCtx: ctx} + if _, err := reader.Read(make([]byte, 1)); !errors.Is(err, context.Canceled) { + t.Fatalf("Reader.Read() error = %v, want context.Canceled", err) + } + if _, err := reader.Seek(0, 0); !errors.Is(err, context.Canceled) { + t.Fatalf("Reader.Seek() error = %v, want context.Canceled", err) + } + + writer := &Writer{cancelCtx: ctx} + if _, err := writer.Write([]byte("data")); !errors.Is(err, context.Canceled) { + t.Fatalf("Writer.Write() error = %v, want context.Canceled", err) + } + + lister := &Lister{cancelCtx: ctx} + if lister.Next() { + t.Fatal("Lister.Next() = true, want false") + } + if !errors.Is(lister.Error(), context.Canceled) { + t.Fatalf("Lister.Error() = %v, want context.Canceled", lister.Error()) + } +} diff --git a/bindings/go/copy.go b/bindings/go/copy.go index 39cb4be0bcc1..184b8eaf7fe0 100644 --- a/bindings/go/copy.go +++ b/bindings/go/copy.go @@ -91,11 +91,13 @@ type copyOptions struct { // Copy duplicates a file from the source path to the destination path. // -// Copy is a wrapper around the C-binding function `opendal_operator_copy`. -// When options are provided, it uses `opendal_operator_copy_with`. +// Copy is a wrapper around the C-binding function `opendal_operator_copy_with_cancel`. +// When options are provided, it uses `opendal_operator_copy_with_options_cancel`. // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - src: The source file path. // - dest: The destination file path. // - opts: Optional copy options. @@ -114,16 +116,18 @@ type copyOptions struct { // # Example // // func exampleCopy(op *opendal.Operator) { -// err := op.Copy("path/from/file", "path/to/file") +// err := op.Copy(context.Background(), "path/from/file", "path/to/file") // if err != nil { // log.Fatal(err) // } // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Copy(src, dest string, opts ...WithCopyFn) error { +func (op *Operator) Copy(ctx context.Context, src, dest string, opts ...WithCopyFn) error { if len(opts) == 0 { - return ffiOperatorCopy.symbol(op.ctx)(op.inner, src, dest) + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorCopyWithCancel.symbol(op.ctx)(op.inner, src, dest, token) + }) } o := ©Options{} @@ -161,7 +165,9 @@ func (op *Operator) Copy(src, dest string, opts ...WithCopyFn) error { if o.chunk != 0 { ffiCopyOptionsSetChunk.symbol(op.ctx)(cOpts, o.chunk) } - err := ffiOperatorCopyWith.symbol(op.ctx)(op.inner, src, dest, cOpts) + err := runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorCopyWithOptionsCancel.symbol(op.ctx)(op.inner, src, dest, cOpts, token) + }) free(cOpts) runtime.KeepAlive(ifMatchData) runtime.KeepAlive(sourceVersionData) @@ -270,12 +276,38 @@ var ffiCopyOptionsSetChunk = newFFI(ffiOpts{ } }) -var ffiOperatorCopyWith = newFFI(ffiOpts{ - sym: "opendal_operator_copy_with", +var ffiOperatorCopyWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_copy_with_cancel", rType: &ffi.TypePointer, aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string, opts *opendalCopyOptions) error { - return func(op *opendalOperator, src, dest string, opts *opendalCopyOptions) error { +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string, token *opendalCancelToken) error { + return func(op *opendalOperator, src, dest string, token *opendalCancelToken) error { + byteSrc, err := BytePtrFromString(src) + if err != nil { + return err + } + byteDest, err := BytePtrFromString(dest) + if err != nil { + return err + } + var e *opendalError + ffiCall( + unsafe.Pointer(&e), + unsafe.Pointer(&op), + unsafe.Pointer(&byteSrc), + unsafe.Pointer(&byteDest), + unsafe.Pointer(&token), + ) + return parseError(ctx, e) + } +}) + +var ffiOperatorCopyWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_copy_with_options_cancel", + rType: &ffi.TypePointer, + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string, opts *opendalCopyOptions, token *opendalCancelToken) error { + return func(op *opendalOperator, src, dest string, opts *opendalCopyOptions, token *opendalCancelToken) error { byteSrc, err := BytePtrFromString(src) if err != nil { return err @@ -291,6 +323,7 @@ var ffiOperatorCopyWith = newFFI(ffiOpts{ unsafe.Pointer(&byteSrc), unsafe.Pointer(&byteDest), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) return parseError(ctx, e) } diff --git a/bindings/go/delete.go b/bindings/go/delete.go index 137f6d0ef3dd..859e790c04fe 100644 --- a/bindings/go/delete.go +++ b/bindings/go/delete.go @@ -59,6 +59,8 @@ type deleteOptions struct { // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - path: The path of the file or directory to delete. // - opts: Optional functional options to configure the delete operation. // @@ -70,48 +72,50 @@ type deleteOptions struct { // // func exampleDelete(op *opendal.Operator) { // // Delete without options -// err := op.Delete("file.txt") +// err := op.Delete(context.Background(), "file.txt") // if err != nil { // log.Printf("Delete operation failed: %v", err) // } // // // Delete with recursive option -// err = op.Delete("dir/", opendal.DeleteWithRecursive(true)) +// err = op.Delete(context.Background(), "dir/", opendal.DeleteWithRecursive(true)) // if err != nil { // log.Printf("Delete operation failed: %v", err) // } // // // Delete a specific version -// err = op.Delete("file.txt", opendal.DeleteWithVersion("v1")) +// err = op.Delete(context.Background(), "file.txt", opendal.DeleteWithVersion("v1")) // if err != nil { // log.Printf("Delete operation failed: %v", err) // } // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Delete(path string, opts ...WithDeleteFn) error { - if len(opts) == 0 { - return ffiOperatorDelete.symbol(op.ctx)(op.inner, path) - } - o := &deleteOptions{} - for _, opt := range opts { - opt(o) - } - cOpts := ffiDeleteOptionsNew.symbol(op.ctx)() - defer ffiDeleteOptionsFree.symbol(op.ctx)(cOpts) - ffiDeleteOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive) - if o.version != nil { - ffiDeleteOptionsSetVersion.symbol(op.ctx)(cOpts, *o.version) - } - return ffiOperatorDeleteWith.symbol(op.ctx)(op.inner, path, cOpts) +func (op *Operator) Delete(ctx context.Context, path string, opts ...WithDeleteFn) error { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + if len(opts) == 0 { + return ffiOperatorDeleteWithCancel.symbol(op.ctx)(op.inner, path, token) + } + o := &deleteOptions{} + for _, opt := range opts { + opt(o) + } + cOpts := ffiDeleteOptionsNew.symbol(op.ctx)() + defer ffiDeleteOptionsFree.symbol(op.ctx)(cOpts) + ffiDeleteOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive) + if o.version != nil { + ffiDeleteOptionsSetVersion.symbol(op.ctx)(cOpts, *o.version) + } + return ffiOperatorDeleteWithOptionsCancel.symbol(op.ctx)(op.inner, path, cOpts, token) + }) } -var ffiOperatorDelete = newFFI(ffiOpts{ - sym: "opendal_operator_delete", +var ffiOperatorDeleteWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_delete_with_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) error { - return func(op *opendalOperator, path string) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) error { + return func(op *opendalOperator, path string, token *opendalCancelToken) error { bytePath, err := BytePtrFromString(path) if err != nil { return err @@ -121,6 +125,7 @@ var ffiOperatorDelete = newFFI(ffiOpts{ unsafe.Pointer(&e), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) return parseError(ctx, e) } @@ -186,12 +191,12 @@ var ffiDeleteOptionsFree = newFFI(ffiOpts{ } }) -var ffiOperatorDeleteWith = newFFI(ffiOpts{ - sym: "opendal_operator_delete_with", +var ffiOperatorDeleteWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_delete_with_options_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalDeleteOptions) error { - return func(op *opendalOperator, path string, opts *opendalDeleteOptions) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalDeleteOptions, token *opendalCancelToken) error { + return func(op *opendalOperator, path string, opts *opendalDeleteOptions, token *opendalCancelToken) error { bytePath, err := BytePtrFromString(path) if err != nil { return err @@ -202,6 +207,7 @@ var ffiOperatorDeleteWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) return parseError(ctx, e) } diff --git a/bindings/go/ffi.go b/bindings/go/ffi.go index 6d44ef33fa34..d2fcf2a2c093 100644 --- a/bindings/go/ffi.go +++ b/bindings/go/ffi.go @@ -36,6 +36,69 @@ type ffiOpts struct { type ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer) +type contextResult[T any] struct { + value T + err error +} + +// runWithCancelContext forwards Go context cancellation to native OpenDAL calls. +// +// When the context is canceled, the native cancel token is signaled and this +// helper blocks until the native call actually returns before reporting +// ctx.Err(). Waiting for the native call to finish guarantees that no native +// code is still touching caller-owned memory or operating on the underlying +// handle after this function returns, so callers can safely reuse buffers and +// close Reader/Writer/Lister handles once it returns. +// +// If the native call still produced a value after cancellation was requested +// (for example, it completed just before observing the token), that value is +// discarded through the optional cleanup callbacks since the caller only +// receives ctx.Err(). +func runWithCancelContext[T any](ctx context.Context, ffiCtx context.Context, fn func(*opendalCancelToken) (T, error), cleanup ...func(T)) (T, error) { + var zero T + if ctx == nil { + ctx = context.Background() + } + if err := ctx.Err(); err != nil { + return zero, err + } + if ctx.Done() == nil { + return fn(nil) + } + + token := ffiCancelTokenNew.symbol(ffiCtx)() + defer ffiCancelTokenFree.symbol(ffiCtx)(token) + + ch := make(chan contextResult[T], 1) + go func() { + value, err := fn(token) + ch <- contextResult[T]{value: value, err: err} + }() + + select { + case result := <-ch: + return result.value, result.err + case <-ctx.Done(): + // Signal cancellation, then block until the native call returns so the + // caller can safely reuse buffers and handles afterwards. + ffiCancelTokenCancel.symbol(ffiCtx)(token) + result := <-ch + for _, cleanupFn := range cleanup { + if cleanupFn != nil { + cleanupFn(result.value) + } + } + return zero, ctx.Err() + } +} + +func runErrWithCancelContext(ctx context.Context, ffiCtx context.Context, fn func(*opendalCancelToken) error) error { + _, err := runWithCancelContext(ctx, ffiCtx, func(token *opendalCancelToken) (struct{}, error) { + return struct{}{}, fn(token) + }) + return err +} + type contextKey string func (c contextKey) String() string { @@ -125,3 +188,40 @@ func newContext(path string) (ctx context.Context, cancel context.CancelFunc, er return } + +var ffiCancelTokenNew = newFFI(ffiOpts{ + sym: "opendal_cancel_token_new", + rType: &ffi.TypePointer, +}, func(_ context.Context, ffiCall ffiCall) func() *opendalCancelToken { + return func() *opendalCancelToken { + var token *opendalCancelToken + ffiCall(unsafe.Pointer(&token)) + return token + } +}) + +var ffiCancelTokenCancel = newFFI(ffiOpts{ + sym: "opendal_cancel_token_cancel", + rType: &ffi.TypeVoid, + aTypes: []*ffi.Type{&ffi.TypePointer}, +}, func(_ context.Context, ffiCall ffiCall) func(token *opendalCancelToken) { + return func(token *opendalCancelToken) { + ffiCall( + nil, + unsafe.Pointer(&token), + ) + } +}) + +var ffiCancelTokenFree = newFFI(ffiOpts{ + sym: "opendal_cancel_token_free", + rType: &ffi.TypeVoid, + aTypes: []*ffi.Type{&ffi.TypePointer}, +}, func(_ context.Context, ffiCall ffiCall) func(token *opendalCancelToken) { + return func(token *opendalCancelToken) { + ffiCall( + nil, + unsafe.Pointer(&token), + ) + } +}) diff --git a/bindings/go/lister.go b/bindings/go/lister.go index 316c3f6b0c93..26a0c30e8389 100644 --- a/bindings/go/lister.go +++ b/bindings/go/lister.go @@ -26,6 +26,55 @@ import ( "github.com/jupiterrider/ffi" ) +// Check verifies if the operator is functioning correctly. +// +// This function performs a health check on the operator by sending a `list` request +// to the root path. It returns any errors encountered during this process. +// +// # Returns +// +// - error: An error if the check fails, or nil if the operator is working correctly. +// +// # Details +// +// The check is performed by attempting to list the contents of the root directory. +// This operation tests the basic functionality of the operator, including +// connectivity and permissions. +// +// # Example +// +// func exampleCheck(op *opendal.Operator) { +// err = op.Check(context.Background()) +// if err != nil { +// log.Printf("Operator check failed: %v", err) +// } else { +// log.Println("Operator is functioning correctly") +// } +// } +// +// Note: This example assumes proper error handling and import statements. +func (op *Operator) Check(ctx context.Context) (err error) { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorCheckWithCancel.symbol(op.ctx)(op.inner, token) + }) +} + +var ffiOperatorCheckWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_check_with_cancel", + rType: &ffi.TypePointer, + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, token *opendalCancelToken) error { + return func(op *opendalOperator, token *opendalCancelToken) error { + var e *opendalError + ffiCall( + unsafe.Pointer(&e), + unsafe.Pointer(&op), + unsafe.Pointer(&token), + ) + return parseError(ctx, e) + } +}) + // List returns a Lister to iterate over entries that start with the given path in the parent directory. // // WithListFn is a functional option for the List operation. @@ -92,6 +141,8 @@ type listOptions struct { // // # Parameters // +// - ctx: The context bound to the returned Lister. It governs cancellation for +// all subsequent Next calls on that Lister. // - path: The starting path for listing entries. // - opts: Optional functional options to configure the list operation. // @@ -104,14 +155,14 @@ type listOptions struct { // // func exampleList(op *opendal.Operator) { // // List without options -// lister, err := op.List("test/") +// lister, err := op.List(context.Background(), "test/") // if err != nil { // log.Fatal(err) // } // defer lister.Close() // // // List with recursive option -// lister, err = op.List("test/", opendal.ListWithRecursive(true)) +// lister, err = op.List(context.Background(), "test/", opendal.ListWithRecursive(true)) // if err != nil { // log.Fatal(err) // } @@ -127,31 +178,39 @@ type listOptions struct { // } // // Note: Always check lister.Error() after the loop to catch any errors that -// occurred during iteration. -func (op *Operator) List(path string, opts ...WithListFn) (*Lister, error) { - o := &listOptions{} - for _, opt := range opts { - opt(o) - } - cOpts := ffiListOptionsNew.symbol(op.ctx)() - defer ffiListOptionsFree.symbol(op.ctx)(cOpts) - ffiListOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive) - if o.limit > 0 { - ffiListOptionsSetLimit.symbol(op.ctx)(cOpts, o.limit) - } - if o.startAfter != nil { - ffiListOptionsSetStartAfter.symbol(op.ctx)(cOpts, *o.startAfter) - } - ffiListOptionsSetVersions.symbol(op.ctx)(cOpts, o.versions) - ffiListOptionsSetDeleted.symbol(op.ctx)(cOpts, o.deleted) - listerInner, err := ffiOperatorListWith.symbol(op.ctx)(op.inner, path, cOpts) - if err != nil { - return nil, err - } - return &Lister{ - inner: listerInner, - ctx: op.ctx, - }, nil +// occurred during iteration. The provided context is bound to the Lister; +// canceling it cancels in-flight Next calls in a blocking manner. +func (op *Operator) List(ctx context.Context, path string, opts ...WithListFn) (*Lister, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (*Lister, error) { + o := &listOptions{} + for _, opt := range opts { + opt(o) + } + cOpts := ffiListOptionsNew.symbol(op.ctx)() + defer ffiListOptionsFree.symbol(op.ctx)(cOpts) + ffiListOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive) + if o.limit > 0 { + ffiListOptionsSetLimit.symbol(op.ctx)(cOpts, o.limit) + } + if o.startAfter != nil { + ffiListOptionsSetStartAfter.symbol(op.ctx)(cOpts, *o.startAfter) + } + ffiListOptionsSetVersions.symbol(op.ctx)(cOpts, o.versions) + ffiListOptionsSetDeleted.symbol(op.ctx)(cOpts, o.deleted) + listerInner, err := ffiOperatorListWithOptionsCancel.symbol(op.ctx)(op.inner, path, cOpts, token) + if err != nil { + return nil, err + } + return &Lister{ + inner: listerInner, + ctx: op.ctx, + cancelCtx: ctx, + }, nil + }, func(lister *Lister) { + if lister != nil { + _ = lister.Close() + } + }) } // Lister provides a mechanism for listing entries at a specified path. @@ -172,7 +231,7 @@ func (op *Operator) List(path string, opts ...WithListFn) (*Lister, error) { // // # Example // -// lister, err := op.List("path/to/list") +// lister, err := op.List(context.Background(), "path/to/list") // if err != nil { // log.Fatal(err) // } @@ -182,11 +241,21 @@ func (op *Operator) List(path string, opts ...WithListFn) (*Lister, error) { // // Process the entry // fmt.Println(entry.Name()) // } +// +// Lister iterates directory entries. +// +// After a cancelled Next the handle remains valid and can be closed without +// leaking resources, but the iterator's internal position is unspecified. +// Callers should discard a Lister that had an operation cancelled and open a +// new one rather than continuing to call Next. type Lister struct { inner *opendalLister ctx context.Context - entry *Entry - err error + // cancelCtx is the user-provided context bound at creation. It governs + // cancellation for Next. + cancelCtx context.Context + entry *Entry + err error } // This method implements the io.Closer interface. It should be called when @@ -228,7 +297,7 @@ func (l *Lister) Error() error { // // # Example // -// lister, err := op.List("path/to/list") +// lister, err := op.List(context.Background(), "path/to/list") // if err != nil { // log.Fatal(err) // } @@ -237,16 +306,27 @@ func (l *Lister) Error() error { // entry := lister.Entry() // fmt.Println(entry.Name()) // } +// +// Next uses the context bound to the Lister at creation time. Canceling that +// context cancels the in-flight advance in a blocking manner. func (l *Lister) Next() bool { - inner, err := ffiListerNext.symbol(l.ctx)(l.inner) - if inner == nil || err != nil { + cancelCtx := l.cancelCtx + if cancelCtx == nil { + cancelCtx = context.Background() + } + entry, err := runWithCancelContext(cancelCtx, l.ctx, func(token *opendalCancelToken) (*Entry, error) { + inner, err := ffiListerNextWithCancel.symbol(l.ctx)(l.inner, token) + if inner == nil || err != nil { + return nil, err + } + return newEntry(l.ctx, inner), nil + }) + if entry == nil || err != nil { l.err = err l.entry = nil return false } - entry := newEntry(l.ctx, inner) - l.entry = entry return true } @@ -436,12 +516,12 @@ var ffiListOptionsFree = newFFI(ffiOpts{ } }) -var ffiOperatorListWith = newFFI(ffiOpts{ - sym: "opendal_operator_list_with", +var ffiOperatorListWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_list_with_options_cancel", rType: &typeResultList, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalListOptions) (*opendalLister, error) { - return func(op *opendalOperator, path string, opts *opendalListOptions) (*opendalLister, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalListOptions, token *opendalCancelToken) (*opendalLister, error) { + return func(op *opendalOperator, path string, opts *opendalListOptions, token *opendalCancelToken) (*opendalLister, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -452,6 +532,7 @@ var ffiOperatorListWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) if result.err != nil { return nil, parseError(ctx, result.err) @@ -473,16 +554,17 @@ var ffiListerFree = newFFI(ffiOpts{ } }) -var ffiListerNext = newFFI(ffiOpts{ - sym: "opendal_lister_next", +var ffiListerNextWithCancel = newFFI(ffiOpts{ + sym: "opendal_lister_next_with_cancel", rType: &typeResultListerNext, - aTypes: []*ffi.Type{&ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(l *opendalLister) (*opendalEntry, error) { - return func(l *opendalLister) (*opendalEntry, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(l *opendalLister, token *opendalCancelToken) (*opendalEntry, error) { + return func(l *opendalLister, token *opendalCancelToken) (*opendalEntry, error) { var result opendalResultListerNext ffiCall( unsafe.Pointer(&result), unsafe.Pointer(&l), + unsafe.Pointer(&token), ) if result.err != nil { return nil, parseError(ctx, result.err) diff --git a/bindings/go/opendal.go b/bindings/go/opendal.go index d550faee72c0..ab8cd75646a1 100644 --- a/bindings/go/opendal.go +++ b/bindings/go/opendal.go @@ -46,7 +46,7 @@ // defer op.Close() // // // Perform operations using the operator -// err = op.Write("example.txt", []byte("Hello, OpenDAL!")) +// err = op.Write(context.Background(), "example.txt", []byte("Hello, OpenDAL!")) // if err != nil { // log.Fatal(err) // } @@ -122,12 +122,12 @@ type OperatorOptions map[string]string // defer op.Close() // Ensure the operator is closed when done // // // Perform operations using the operator -// err = op.Write("example.txt", []byte("Hello, OpenDAL!")) +// err = op.Write(context.Background(), "example.txt", []byte("Hello, OpenDAL!")) // if err != nil { // log.Fatal(err) // } // -// data, err := op.Read("example.txt") +// data, err := op.Read(context.Background(), "example.txt") // if err != nil { // log.Fatal(err) // } diff --git a/bindings/go/operator.go b/bindings/go/operator.go index 5bb31374b36f..e3a9125fb419 100644 --- a/bindings/go/operator.go +++ b/bindings/go/operator.go @@ -33,6 +33,8 @@ import ( // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - from: The current file path. // - to: The new file path. // @@ -49,7 +51,7 @@ import ( // # Example // // func exampleRename(op *opendal.Operator) { -// err = op.Rename("path/from/file", "path/to/file") +// err = op.Rename(context.Background(), "path/from/file", "path/to/file") // if err != nil { // log.Printf("Rename operation failed: %v", err) // } else { @@ -58,34 +60,10 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Rename(src, dest string) error { - return ffiOperatorRename.symbol(op.ctx)(op.inner, src, dest) -} - -// Check verifies if the operator is functioning correctly. -// -// Check is a wrapper around the C-binding function `opendal_operator_check`. -// It performs a health check against the underlying backend, returning any -// error encountered while reaching it. -// -// # Returns -// -// - error: An error if the check fails, or nil if the operator is working correctly. -// -// # Example -// -// func exampleCheck(op *opendal.Operator) { -// err = op.Check() -// if err != nil { -// log.Printf("Operator check failed: %v", err) -// } else { -// log.Println("Operator is functioning correctly") -// } -// } -// -// Note: This example assumes proper error handling and import statements. -func (op *Operator) Check() error { - return ffiOperatorCheck.symbol(op.ctx)(op.inner) +func (op *Operator) Rename(ctx context.Context, src, dest string) error { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorRenameWithCancel.symbol(op.ctx)(op.inner, src, dest, token) + }) } func normalizeScheme(scheme Scheme) (*byte, error) { @@ -185,41 +163,12 @@ var ffiOperatorOptionsFree = newFFI(ffiOpts{ } }) -var ffiOperatorCopy = newFFI(ffiOpts{ - sym: "opendal_operator_copy", - rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string) (err error) { - return func(op *opendalOperator, src, dest string) (err error) { - var ( - byteSrc *byte - byteDest *byte - ) - byteSrc, err = BytePtrFromString(src) - if err != nil { - return err - } - byteDest, err = BytePtrFromString(dest) - if err != nil { - return err - } - var e *opendalError - ffiCall( - unsafe.Pointer(&e), - unsafe.Pointer(&op), - unsafe.Pointer(&byteSrc), - unsafe.Pointer(&byteDest), - ) - return parseError(ctx, e) - } -}) - -var ffiOperatorRename = newFFI(ffiOpts{ - sym: "opendal_operator_rename", +var ffiOperatorRenameWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_rename_with_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string) (err error) { - return func(op *opendalOperator, src, dest string) (err error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, src, dest string, token *opendalCancelToken) (err error) { + return func(op *opendalOperator, src, dest string, token *opendalCancelToken) (err error) { var ( byteSrc *byte byteDest *byte @@ -238,21 +187,7 @@ var ffiOperatorRename = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&byteSrc), unsafe.Pointer(&byteDest), - ) - return parseError(ctx, e) - } -}) - -var ffiOperatorCheck = newFFI(ffiOpts{ - sym: "opendal_operator_check", - rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator) error { - return func(op *opendalOperator) error { - var e *opendalError - ffiCall( - unsafe.Pointer(&e), - unsafe.Pointer(&op), + unsafe.Pointer(&token), ) return parseError(ctx, e) } diff --git a/bindings/go/presign.go b/bindings/go/presign.go index 4c506829ac8c..cf8d973acb14 100644 --- a/bindings/go/presign.go +++ b/bindings/go/presign.go @@ -29,41 +29,59 @@ import ( "github.com/jupiterrider/ffi" ) -type presignFunc func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) +type presignFunc func(op *opendalOperator, path string, expire uint64, token *opendalCancelToken) (*opendalPresignedRequest, error) // PresignRead returns a presigned HTTP request that can be used to read the object at the given path. -func (op *Operator) PresignRead(path string, expire time.Duration) (*http.Request, error) { - return op.presign(path, expire, ffiOperatorPresignRead.symbol(op.ctx)) +// +// Canceling ctx cancels the underlying native call in a blocking manner. +func (op *Operator) PresignRead(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { + return op.presignContext(ctx, path, expire, func() presignFunc { + return ffiOperatorPresignReadWithCancel.symbol(op.ctx) + }) } // PresignWrite returns a presigned HTTP request that can be used to write the object at the given path. -func (op *Operator) PresignWrite(path string, expire time.Duration) (*http.Request, error) { - return op.presign(path, expire, ffiOperatorPresignWrite.symbol(op.ctx)) +// +// Canceling ctx cancels the underlying native call in a blocking manner. +func (op *Operator) PresignWrite(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { + return op.presignContext(ctx, path, expire, func() presignFunc { + return ffiOperatorPresignWriteWithCancel.symbol(op.ctx) + }) } // PresignDelete returns a presigned HTTP request that can be used to delete the object at the given path. -func (op *Operator) PresignDelete(path string, expire time.Duration) (*http.Request, error) { - return op.presign(path, expire, ffiOperatorPresignDelete.symbol(op.ctx)) +// +// Canceling ctx cancels the underlying native call in a blocking manner. +func (op *Operator) PresignDelete(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { + return op.presignContext(ctx, path, expire, func() presignFunc { + return ffiOperatorPresignDeleteWithCancel.symbol(op.ctx) + }) } // PresignStat returns a presigned HTTP request that can be used to stat the object at the given path. -func (op *Operator) PresignStat(path string, expire time.Duration) (*http.Request, error) { - return op.presign(path, expire, ffiOperatorPresignStat.symbol(op.ctx)) +// +// Canceling ctx cancels the underlying native call in a blocking manner. +func (op *Operator) PresignStat(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { + return op.presignContext(ctx, path, expire, func() presignFunc { + return ffiOperatorPresignStatWithCancel.symbol(op.ctx) + }) } -func (op *Operator) presign(path string, expire time.Duration, call presignFunc) (*http.Request, error) { - secs := uint64(expire / time.Second) +func (op *Operator) presignContext(ctx context.Context, path string, expire time.Duration, call func() presignFunc) (*http.Request, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (*http.Request, error) { + secs := uint64(expire / time.Second) - req, err := call(op.inner, path, secs) - if err != nil { - return nil, err - } - if req == nil { - return nil, fmt.Errorf("presigned request should not be nil") - } - defer ffiPresignedRequestFree.symbol(op.ctx)(req) + req, err := call()(op.inner, path, secs, token) + if err != nil { + return nil, err + } + if req == nil { + return nil, fmt.Errorf("presigned request should not be nil") + } + defer ffiPresignedRequestFree.symbol(op.ctx)(req) - return buildHTTPPresignedRequest(op.ctx, req) + return buildHTTPPresignedRequest(op.ctx, req) + }) } func buildHTTPPresignedRequest(ctx context.Context, ptr *opendalPresignedRequest) (req *http.Request, err error) { @@ -97,101 +115,37 @@ func buildHTTPPresignedRequest(ctx context.Context, ptr *opendalPresignedRequest return } -var ffiOperatorPresignRead = newFFI(ffiOpts{ - sym: "opendal_operator_presign_read", - rType: &typeResultPresign, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypeUint64}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - return func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - bytePath, err := BytePtrFromString(path) - if err != nil { - return nil, err - } - var result resultPresign - ffiCall( - unsafe.Pointer(&result), - unsafe.Pointer(&op), - unsafe.Pointer(&bytePath), - unsafe.Pointer(&expire), - ) - if result.error != nil { - return nil, parseError(ctx, result.error) - } - return result.req, nil - } -}) - -var ffiOperatorPresignWrite = newFFI(ffiOpts{ - sym: "opendal_operator_presign_write", - rType: &typeResultPresign, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypeUint64}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - return func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - bytePath, err := BytePtrFromString(path) - if err != nil { - return nil, err - } - var result resultPresign - ffiCall( - unsafe.Pointer(&result), - unsafe.Pointer(&op), - unsafe.Pointer(&bytePath), - unsafe.Pointer(&expire), - ) - if result.error != nil { - return nil, parseError(ctx, result.error) +func newPresignWithCancelFFI(sym string) *FFI[presignFunc] { + return newFFI(ffiOpts{ + sym: contextKey(sym), + rType: &typeResultPresign, + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypeUint64, &ffi.TypePointer}, + }, func(ctx context.Context, ffiCall ffiCall) presignFunc { + return func(op *opendalOperator, path string, expire uint64, token *opendalCancelToken) (*opendalPresignedRequest, error) { + bytePath, err := BytePtrFromString(path) + if err != nil { + return nil, err + } + var result resultPresign + ffiCall( + unsafe.Pointer(&result), + unsafe.Pointer(&op), + unsafe.Pointer(&bytePath), + unsafe.Pointer(&expire), + unsafe.Pointer(&token), + ) + if result.error != nil { + return nil, parseError(ctx, result.error) + } + return result.req, nil } - return result.req, nil - } -}) - -var ffiOperatorPresignDelete = newFFI(ffiOpts{ - sym: "opendal_operator_presign_delete", - rType: &typeResultPresign, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypeUint64}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - return func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - bytePath, err := BytePtrFromString(path) - if err != nil { - return nil, err - } - var result resultPresign - ffiCall( - unsafe.Pointer(&result), - unsafe.Pointer(&op), - unsafe.Pointer(&bytePath), - unsafe.Pointer(&expire), - ) - if result.error != nil { - return nil, parseError(ctx, result.error) - } - return result.req, nil - } -}) + }) +} -var ffiOperatorPresignStat = newFFI(ffiOpts{ - sym: "opendal_operator_presign_stat", - rType: &typeResultPresign, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypeUint64}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - return func(op *opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) { - bytePath, err := BytePtrFromString(path) - if err != nil { - return nil, err - } - var result resultPresign - ffiCall( - unsafe.Pointer(&result), - unsafe.Pointer(&op), - unsafe.Pointer(&bytePath), - unsafe.Pointer(&expire), - ) - if result.error != nil { - return nil, parseError(ctx, result.error) - } - return result.req, nil - } -}) +var ffiOperatorPresignReadWithCancel = newPresignWithCancelFFI("opendal_operator_presign_read_with_cancel") +var ffiOperatorPresignWriteWithCancel = newPresignWithCancelFFI("opendal_operator_presign_write_with_cancel") +var ffiOperatorPresignDeleteWithCancel = newPresignWithCancelFFI("opendal_operator_presign_delete_with_cancel") +var ffiOperatorPresignStatWithCancel = newPresignWithCancelFFI("opendal_operator_presign_stat_with_cancel") var ffiPresignedRequestMethod = newFFI(ffiOpts{ sym: "opendal_presigned_request_method", diff --git a/bindings/go/reader.go b/bindings/go/reader.go index dcb108d6cd51..0452f8944c50 100644 --- a/bindings/go/reader.go +++ b/bindings/go/reader.go @@ -36,6 +36,9 @@ import ( // // # Parameters // +// - ctx: The context for the operation. When the context is canceled or its +// deadline is exceeded, the underlying native read is canceled and Read +// returns ctx.Err() after the native call has stopped. // - path: The path of the file to read. // - opts: Optional read options. // @@ -52,7 +55,7 @@ import ( // # Example // // func exampleRead(op *opendal.Operator) { -// data, err := op.Read("test") +// data, err := op.Read(context.Background(), "test") // if err != nil { // log.Fatal(err) // } @@ -60,8 +63,14 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Read(path string, opts ...WithReadFn) ([]byte, error) { - bytes, err := op.read(path, opts...) +func (op *Operator) Read(ctx context.Context, path string, opts ...WithReadFn) ([]byte, error) { + bytes, err := runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (opendalBytes, error) { + return op.readWithCancel(path, token, opts...) + }, func(bytes opendalBytes) { + if bytes.data != nil { + ffiBytesFree.symbol(op.ctx)(&bytes) + } + }) if err != nil { return nil, err } @@ -73,9 +82,9 @@ func (op *Operator) Read(path string, opts ...WithReadFn) ([]byte, error) { return data, nil } -func (op *Operator) read(path string, opts ...WithReadFn) (opendalBytes, error) { +func (op *Operator) readWithCancel(path string, token *opendalCancelToken, opts ...WithReadFn) (opendalBytes, error) { if len(opts) == 0 { - return ffiOperatorRead.symbol(op.ctx)(op.inner, path) + return ffiOperatorReadWithCancel.symbol(op.ctx)(op.inner, path, token) } o := parseReadOptions(opts...) @@ -84,7 +93,7 @@ func (op *Operator) read(path string, opts ...WithReadFn) (opendalBytes, error) return opendalBytes{}, err } defer ffiReadOptionsFree.symbol(op.ctx)(cOpts) - bytes, err := ffiOperatorReadWith.symbol(op.ctx)(op.inner, path, cOpts) + bytes, err := ffiOperatorReadWithOptionsCancel.symbol(op.ctx)(op.inner, path, cOpts, token) runtime.KeepAlive(keepAlive) return bytes, err } @@ -311,6 +320,8 @@ func newOpendalReadOptions(ctx context.Context, o *readOptions) (*opendalReadOpt // // # Parameters // +// - ctx: The context bound to the returned Reader. It governs cancellation for +// all subsequent Read and Seek calls on that Reader. // - path: The path of the file to read. // // # Returns @@ -322,11 +333,13 @@ func newOpendalReadOptions(ctx context.Context, o *readOptions) (*opendalReadOpt // // - This implementation does not support the `reader_with` functionality. // - The returned reader allows for more controlled and efficient reading of large files. +// - The provided context is bound to the Reader; canceling it cancels in-flight +// Read and Seek calls in a blocking manner. // // # Example // // func exampleReader(op *opendal.Operator) { -// r, err := op.Reader("path/to/file") +// r, err := op.Reader(context.Background(), "path/to/file") // if err != nil { // log.Fatal(err) // } @@ -345,21 +358,38 @@ func newOpendalReadOptions(ctx context.Context, o *readOptions) (*opendalReadOpt // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Reader(path string) (*Reader, error) { - inner, err := ffiOperatorReader.symbol(op.ctx)(op.inner, path) - if err != nil { - return nil, err - } - reader := &Reader{ - inner: inner, - ctx: op.ctx, - } - return reader, nil +func (op *Operator) Reader(ctx context.Context, path string) (*Reader, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (*Reader, error) { + inner, err := ffiOperatorReaderWithCancel.symbol(op.ctx)(op.inner, path, token) + if err != nil { + return nil, err + } + reader := &Reader{ + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, + } + return reader, nil + }, func(reader *Reader) { + if reader != nil { + _ = reader.Close() + } + }) } +// Reader implements io.ReadSeekCloser. +// +// After a cancelled Read or Seek the handle remains valid and can be closed +// without leaking resources, but its internal stream position is unspecified. +// Callers should discard a Reader that had an operation cancelled and open a +// new one rather than attempting to resume reading from the same handle. type Reader struct { inner *opendalReader ctx context.Context + // cancelCtx is the user-provided context bound at creation. It governs + // cancellation for Read and Seek so the Reader keeps stdlib io interface + // signatures. + cancelCtx context.Context } var _ io.ReadSeekCloser = (*Reader)(nil) @@ -385,7 +415,7 @@ var _ io.ReadSeekCloser = (*Reader)(nil) // // # Example // -// reader, err := op.Reader("path/to/file") +// reader, err := op.Reader(context.Background(), "path/to/file") // if err != nil { // log.Fatal(err) // } @@ -404,28 +434,33 @@ var _ io.ReadSeekCloser = (*Reader)(nil) // } // // Note: Always check the number of bytes read (n) as it may be less than len(buf). +// +// Read uses the context bound to the Reader at creation time. Canceling that +// context cancels the in-flight read in a blocking manner. func (r *Reader) Read(buf []byte) (int, error) { - length := uint(len(buf)) - if length == 0 { - return 0, nil - } - read := ffiReaderRead.symbol(r.ctx) - var ( - totalSize uint - size uint - err error - ) - for { - size, err = read(r.inner, buf[totalSize:]) - totalSize += size - if size == 0 || err != nil || totalSize >= length { - break + return runWithCancelContext(r.cancelCtx, r.ctx, func(token *opendalCancelToken) (int, error) { + length := uint(len(buf)) + if length == 0 { + return 0, nil } - } - if totalSize == 0 && err == nil { - err = io.EOF - } - return int(totalSize), err + read := ffiReaderReadWithCancel.symbol(r.ctx) + var ( + totalSize uint + size uint + err error + ) + for { + size, err = read(r.inner, buf[totalSize:], token) + totalSize += size + if size == 0 || err != nil || totalSize >= length { + break + } + } + if totalSize == 0 && err == nil { + err = io.EOF + } + return int(totalSize), err + }) } // Seek sets the offset for the next Read operation on the reader. @@ -447,7 +482,7 @@ func (r *Reader) Read(buf []byte) (int, error) { // // # Example // -// reader, err := op.Reader("path/to/file") +// reader, err := op.Reader(context.Background(), "path/to/file") // if err != nil { // log.Fatal(err) // } @@ -469,8 +504,12 @@ func (r *Reader) Read(buf []byte) (int, error) { // // Note: The actual new position may differ from the requested position // if the underlying storage system has restrictions on seeking. +// +// Seek uses the context bound to the Reader at creation time. func (r *Reader) Seek(offset int64, whence int) (int64, error) { - return ffiReaderSeek.symbol(r.ctx)(r.inner, offset, whence) + return runWithCancelContext(r.cancelCtx, r.ctx, func(token *opendalCancelToken) (int64, error) { + return ffiReaderSeekWithCancel.symbol(r.ctx)(r.inner, offset, whence, token) + }) } // Close releases resources associated with the OperatorReader. @@ -479,12 +518,12 @@ func (r *Reader) Close() error { return nil } -var ffiOperatorRead = newFFI(ffiOpts{ - sym: "opendal_operator_read", +var ffiOperatorReadWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_read_with_cancel", rType: &typeResultRead, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (opendalBytes, error) { - return func(op *opendalOperator, path string) (opendalBytes, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) (opendalBytes, error) { + return func(op *opendalOperator, path string, token *opendalCancelToken) (opendalBytes, error) { bytePath, err := BytePtrFromString(path) if err != nil { return opendalBytes{}, err @@ -494,17 +533,18 @@ var ffiOperatorRead = newFFI(ffiOpts{ unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) return result.data, parseError(ctx, result.error) } }) -var ffiOperatorReadWith = newFFI(ffiOpts{ - sym: "opendal_operator_read_with", +var ffiOperatorReadWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_read_with_options_cancel", rType: &typeResultRead, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalReadOptions) (opendalBytes, error) { - return func(op *opendalOperator, path string, opts *opendalReadOptions) (opendalBytes, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalReadOptions, token *opendalCancelToken) (opendalBytes, error) { + return func(op *opendalOperator, path string, opts *opendalReadOptions, token *opendalCancelToken) (opendalBytes, error) { bytePath, err := BytePtrFromString(path) if err != nil { return opendalBytes{}, err @@ -515,6 +555,7 @@ var ffiOperatorReadWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) return result.data, parseError(ctx, result.error) } @@ -646,12 +687,12 @@ var ffiReadOptionsSetContentLengthHint = newFFI(ffiOpts{ } }) -var ffiOperatorReader = newFFI(ffiOpts{ - sym: "opendal_operator_reader", +var ffiOperatorReaderWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_reader_with_cancel", rType: &typeResultOperatorReader, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (*opendalReader, error) { - return func(op *opendalOperator, path string) (*opendalReader, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalReader, error) { + return func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalReader, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -661,6 +702,7 @@ var ffiOperatorReader = newFFI(ffiOpts{ unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) if result.error != nil { return nil, parseError(ctx, result.error) @@ -682,12 +724,12 @@ var ffiReaderFree = newFFI(ffiOpts{ } }) -var ffiReaderRead = newFFI(ffiOpts{ - sym: "opendal_reader_read", +var ffiReaderReadWithCancel = newFFI(ffiOpts{ + sym: "opendal_reader_read_with_cancel", rType: &typeResultReaderRead, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, buf []byte) (size uint, err error) { - return func(r *opendalReader, buf []byte) (size uint, err error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, buf []byte, token *opendalCancelToken) (size uint, err error) { + return func(r *opendalReader, buf []byte, token *opendalCancelToken) (size uint, err error) { var length = len(buf) if length == 0 { return 0, nil @@ -699,6 +741,7 @@ var ffiReaderRead = newFFI(ffiOpts{ unsafe.Pointer(&r), unsafe.Pointer(&bytePtr), unsafe.Pointer(&length), + unsafe.Pointer(&token), ) if result.error != nil { return 0, parseError(ctx, result.error) @@ -707,18 +750,19 @@ var ffiReaderRead = newFFI(ffiOpts{ } }) -var ffiReaderSeek = newFFI(ffiOpts{ - sym: "opendal_reader_seek", +var ffiReaderSeekWithCancel = newFFI(ffiOpts{ + sym: "opendal_reader_seek_with_cancel", rType: &typeResultReaderSeek, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, offset int64, whence int) (int64, error) { - return func(r *opendalReader, offset int64, whence int) (int64, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalReader, offset int64, whence int, token *opendalCancelToken) (int64, error) { + return func(r *opendalReader, offset int64, whence int, token *opendalCancelToken) (int64, error) { var result resultReaderSeek ffiCall( unsafe.Pointer(&result), unsafe.Pointer(&r), unsafe.Pointer(&offset), unsafe.Pointer(&whence), + unsafe.Pointer(&token), ) if result.error != nil { return 0, parseError(ctx, result.error) diff --git a/bindings/go/stat.go b/bindings/go/stat.go index 715b8df57290..6ebc36671fa2 100644 --- a/bindings/go/stat.go +++ b/bindings/go/stat.go @@ -35,6 +35,8 @@ import ( // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - path: The path of the file or directory to get metadata for. // - opts: Optional stat options. // @@ -50,7 +52,7 @@ import ( // # Example // // func exampleStat(op *opendal.Operator) { -// meta, err := op.Stat("/path/to/file") +// meta, err := op.Stat(context.Background(), "/path/to/file") // if err != nil { // if e, ok := err.(*opendal.Error); ok && e.Code() == opendal.CodeNotFound { // fmt.Println("File not found") @@ -63,27 +65,29 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Stat(path string, opts ...WithStatFn) (*Metadata, error) { - if len(opts) == 0 { - meta, err := ffiOperatorStat.symbol(op.ctx)(op.inner, path) +func (op *Operator) Stat(ctx context.Context, path string, opts ...WithStatFn) (*Metadata, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (*Metadata, error) { + if len(opts) == 0 { + meta, err := ffiOperatorStatWithCancel.symbol(op.ctx)(op.inner, path, token) + if err != nil { + return nil, err + } + return newMetadata(op.ctx, meta), nil + } + + o := parseStatOptions(opts...) + cOpts, keepAlive, err := newOpendalStatOptions(op.ctx, o) + if err != nil { + return nil, err + } + defer ffiStatOptionsFree.symbol(op.ctx)(cOpts) + meta, err := ffiOperatorStatWithOptionsCancel.symbol(op.ctx)(op.inner, path, cOpts, token) + runtime.KeepAlive(keepAlive) if err != nil { return nil, err } return newMetadata(op.ctx, meta), nil - } - - o := parseStatOptions(opts...) - cOpts, keepAlive, err := newOpendalStatOptions(op.ctx, o) - if err != nil { - return nil, err - } - defer ffiStatOptionsFree.symbol(op.ctx)(cOpts) - meta, err := ffiOperatorStatWith.symbol(op.ctx)(op.inner, path, cOpts) - runtime.KeepAlive(keepAlive) - if err != nil { - return nil, err - } - return newMetadata(op.ctx, meta), nil + }) } // WithStatFn is a functional option for stat operations. @@ -225,6 +229,8 @@ func newOpendalStatOptions(ctx context.Context, o *statOptions) (*opendalStatOpt // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - path: The path of the file or directory to check. // // # Returns @@ -235,7 +241,7 @@ func newOpendalStatOptions(ctx context.Context, o *statOptions) (*opendalStatOpt // // # Example // -// exists, err := op.IsExist("path/to/file") +// exists, err := op.IsExist(context.Background(), "path/to/file") // if err != nil { // log.Fatalf("Error checking existence: %v", err) // } @@ -244,16 +250,18 @@ func newOpendalStatOptions(ctx context.Context, o *statOptions) (*opendalStatOpt // } else { // fmt.Println("The file does not exist") // } -func (op *Operator) IsExist(path string) (bool, error) { - return ffiOperatorIsExist.symbol(op.ctx)(op.inner, path) +func (op *Operator) IsExist(ctx context.Context, path string) (bool, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (bool, error) { + return ffiOperatorExistsWithCancel.symbol(op.ctx)(op.inner, path, token) + }) } -var ffiOperatorStat = newFFI(ffiOpts{ - sym: "opendal_operator_stat", +var ffiOperatorStatWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_stat_with_cancel", rType: &typeResultStat, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (*opendalMetadata, error) { - return func(op *opendalOperator, path string) (*opendalMetadata, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalMetadata, error) { + return func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalMetadata, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -263,6 +271,7 @@ var ffiOperatorStat = newFFI(ffiOpts{ unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) if result.error != nil { return nil, parseError(ctx, result.error) @@ -271,12 +280,12 @@ var ffiOperatorStat = newFFI(ffiOpts{ } }) -var ffiOperatorStatWith = newFFI(ffiOpts{ - sym: "opendal_operator_stat_with", +var ffiOperatorStatWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_stat_with_options_cancel", rType: &typeResultStat, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalStatOptions) (*opendalMetadata, error) { - return func(op *opendalOperator, path string, opts *opendalStatOptions) (*opendalMetadata, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalStatOptions, token *opendalCancelToken) (*opendalMetadata, error) { + return func(op *opendalOperator, path string, opts *opendalStatOptions, token *opendalCancelToken) (*opendalMetadata, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -287,6 +296,7 @@ var ffiOperatorStatWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) if result.error != nil { return nil, parseError(ctx, result.error) @@ -295,26 +305,29 @@ var ffiOperatorStatWith = newFFI(ffiOpts{ } }) -var ffiOperatorIsExist = newFFI(ffiOpts{ - sym: "opendal_operator_is_exist", - rType: &typeResultIsExist, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (bool, error) { - return func(op *opendalOperator, path string) (bool, error) { +// ffiOperatorExistsWithCancel binds opendal_operator_exists_with_cancel, the +// non-deprecated replacement for opendal_operator_is_exist_with_cancel. +var ffiOperatorExistsWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_exists_with_cancel", + rType: &typeResultExists, + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) (bool, error) { + return func(op *opendalOperator, path string, token *opendalCancelToken) (bool, error) { bytePath, err := BytePtrFromString(path) if err != nil { return false, err } - var result resultIsExist + var result resultExists ffiCall( unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) if result.error != nil { return false, parseError(ctx, result.error) } - return result.is_exist == 1, nil + return result.exists == 1, nil } }) diff --git a/bindings/go/string_ownership_test.go b/bindings/go/string_ownership_test.go index 10074580da3a..769a0d10798a 100644 --- a/bindings/go/string_ownership_test.go +++ b/bindings/go/string_ownership_test.go @@ -683,26 +683,26 @@ func TestWriteWithOptions(t *testing.T) { } } -func TestFfiOperatorWriteWithArgTypes(t *testing.T) { - aTypes := ffiOperatorWriteWith.opts.aTypes - if len(aTypes) != 4 { - t.Fatalf("ffiOperatorWriteWith aTypes len = %d, want 4", len(aTypes)) +func TestFfiOperatorWriteWithOptionsCancelArgTypes(t *testing.T) { + aTypes := ffiOperatorWriteWithOptionsCancel.opts.aTypes + if len(aTypes) != 5 { + t.Fatalf("ffiOperatorWriteWithOptionsCancel aTypes len = %d, want 5", len(aTypes)) } for i, at := range aTypes { if at != &ffi.TypePointer { - t.Fatalf("ffiOperatorWriteWith aTypes[%d] = %v, want TypePointer", i, at) + t.Fatalf("ffiOperatorWriteWithOptionsCancel aTypes[%d] = %v, want TypePointer", i, at) } } } -func TestFfiOperatorWriterWithArgTypes(t *testing.T) { - aTypes := ffiOperatorWriterWith.opts.aTypes - if len(aTypes) != 3 { - t.Fatalf("ffiOperatorWriterWith aTypes len = %d, want 3", len(aTypes)) +func TestFfiOperatorWriterWithOptionsCancelArgTypes(t *testing.T) { + aTypes := ffiOperatorWriterWithOptionsCancel.opts.aTypes + if len(aTypes) != 4 { + t.Fatalf("ffiOperatorWriterWithOptionsCancel aTypes len = %d, want 4", len(aTypes)) } for i, at := range aTypes { if at != &ffi.TypePointer { - t.Fatalf("ffiOperatorWriterWith aTypes[%d] = %v, want TypePointer", i, at) + t.Fatalf("ffiOperatorWriterWithOptionsCancel aTypes[%d] = %v, want TypePointer", i, at) } } } @@ -865,20 +865,20 @@ func TestListWithDeletedFalse(t *testing.T) { } } -func TestFfiOperatorListWithReturnType(t *testing.T) { - if ffiOperatorListWith.opts.rType != &typeResultList { - t.Fatalf("ffiOperatorListWith rType = %v, want typeResultList", ffiOperatorListWith.opts.rType) +func TestFfiOperatorListWithOptionsCancelReturnType(t *testing.T) { + if ffiOperatorListWithOptionsCancel.opts.rType != &typeResultList { + t.Fatalf("ffiOperatorListWithOptionsCancel rType = %v, want typeResultList", ffiOperatorListWithOptionsCancel.opts.rType) } } -func TestFfiOperatorListWithArgTypes(t *testing.T) { - aTypes := ffiOperatorListWith.opts.aTypes - if len(aTypes) != 3 { - t.Fatalf("ffiOperatorListWith aTypes len = %d, want 3", len(aTypes)) +func TestFfiOperatorListWithOptionsCancelArgTypes(t *testing.T) { + aTypes := ffiOperatorListWithOptionsCancel.opts.aTypes + if len(aTypes) != 4 { + t.Fatalf("ffiOperatorListWithOptionsCancel aTypes len = %d, want 4", len(aTypes)) } for i, at := range aTypes { if at != &ffi.TypePointer { - t.Fatalf("ffiOperatorListWith aTypes[%d] = %v, want TypePointer", i, at) + t.Fatalf("ffiOperatorListWithOptionsCancel aTypes[%d] = %v, want TypePointer", i, at) } } } @@ -976,20 +976,20 @@ func TestReadWithRangeFrom(t *testing.T) { } } -func TestFfiOperatorReadWithReturnType(t *testing.T) { - if ffiOperatorReadWith.opts.rType != &typeResultRead { - t.Fatalf("ffiOperatorReadWith rType = %v, want typeResultRead", ffiOperatorReadWith.opts.rType) +func TestFfiOperatorReadWithOptionsCancelReturnType(t *testing.T) { + if ffiOperatorReadWithOptionsCancel.opts.rType != &typeResultRead { + t.Fatalf("ffiOperatorReadWithOptionsCancel rType = %v, want typeResultRead", ffiOperatorReadWithOptionsCancel.opts.rType) } } -func TestFfiOperatorReadWithArgTypes(t *testing.T) { - aTypes := ffiOperatorReadWith.opts.aTypes - if len(aTypes) != 3 { - t.Fatalf("ffiOperatorReadWith aTypes len = %d, want 3", len(aTypes)) +func TestFfiOperatorReadWithOptionsCancelArgTypes(t *testing.T) { + aTypes := ffiOperatorReadWithOptionsCancel.opts.aTypes + if len(aTypes) != 4 { + t.Fatalf("ffiOperatorReadWithOptionsCancel aTypes len = %d, want 4", len(aTypes)) } for i, at := range aTypes { if at != &ffi.TypePointer { - t.Fatalf("ffiOperatorReadWith aTypes[%d] = %v, want TypePointer", i, at) + t.Fatalf("ffiOperatorReadWithOptionsCancel aTypes[%d] = %v, want TypePointer", i, at) } } } diff --git a/bindings/go/tests/behavior_tests/benchmark_test.go b/bindings/go/tests/behavior_tests/benchmark_test.go index 830e95103247..8a323cf783a3 100644 --- a/bindings/go/tests/behavior_tests/benchmark_test.go +++ b/bindings/go/tests/behavior_tests/benchmark_test.go @@ -21,6 +21,7 @@ package opendal_test import ( "bytes" + "context" "fmt" "io" "os" @@ -137,11 +138,11 @@ func NewOpenDALReadWriter(op *opendal.Operator) ReadWriter { } func (rw *OpenDALReadWriter) Write(path string, data []byte) error { - return rw.Operator.Write(path, data) + return rw.Operator.Write(context.Background(), path, data) } func (rw *OpenDALReadWriter) Read(path string) ([]byte, error) { - return rw.Operator.Read(path) + return rw.Operator.Read(context.Background(), path) } func (rw *OpenDALReadWriter) Name() string { diff --git a/bindings/go/tests/behavior_tests/copy_test.go b/bindings/go/tests/behavior_tests/copy_test.go index eaf812694f3b..45de9b20f886 100644 --- a/bindings/go/tests/behavior_tests/copy_test.go +++ b/bindings/go/tests/behavior_tests/copy_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "fmt" opendal "github.com/apache/opendal/bindings/go" @@ -60,13 +61,13 @@ func testsCopy(cap *opendal.Capability) []behaviorTest { func testCopyFileWithASCIIName(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.NewFilePath() - assert.Nil(op.Copy(sourcePath, targetPath)) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } @@ -75,10 +76,10 @@ func testCopyFileWithNonASCIIName(assert *require.Assertions, op *opendal.Operat sourcePath, sourceContent, _ := fixture.NewFileWithPath("🐂🍺中文.docx") targetPath := fixture.PushPath("😈🐅Français.docx") - assert.Nil(op.Write(sourcePath, sourceContent)) - assert.Nil(op.Copy(sourcePath, targetPath)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } @@ -87,7 +88,7 @@ func testCopyNonExistingSource(assert *require.Assertions, op *opendal.Operator, sourcePath := uuid.NewString() targetPath := uuid.NewString() - err := op.Copy(sourcePath, targetPath) + err := op.Copy(context.Background(), sourcePath, targetPath) assert.NotNil(err, "copy must fail") assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } @@ -96,9 +97,9 @@ func testCopySourceDir(assert *require.Assertions, op *opendal.Operator, fixture sourcePath := fixture.NewDirPath() targetPath := uuid.NewString() - assert.Nil(op.CreateDir(sourcePath)) + assert.Nil(op.CreateDir(context.Background(), sourcePath)) - err := op.Copy(sourcePath, targetPath) + err := op.Copy(context.Background(), sourcePath, targetPath) assert.NotNil(err, "copy must fail") assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -106,13 +107,13 @@ func testCopySourceDir(assert *require.Assertions, op *opendal.Operator, fixture func testCopyTargetDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.NewDirPath() - assert.Nil(op.CreateDir(targetPath)) + assert.Nil(op.CreateDir(context.Background(), targetPath)) - err := op.Copy(sourcePath, targetPath) + err := op.Copy(context.Background(), sourcePath, targetPath) assert.NotNil(err, "copy must fail") assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -120,9 +121,9 @@ func testCopyTargetDir(assert *require.Assertions, op *opendal.Operator, fixture func testCopySelf(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) - err := op.Copy(sourcePath, sourcePath) + err := op.Copy(context.Background(), sourcePath, sourcePath) assert.NotNil(err, "copy must fail") assert.Equal(opendal.CodeIsSameFile, assertErrorCode(err)) } @@ -130,7 +131,7 @@ func testCopySelf(assert *require.Assertions, op *opendal.Operator, fixture *fix func testCopyNested(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.PushPath(fmt.Sprintf( "%s/%s/%s", @@ -139,9 +140,9 @@ func testCopyNested(assert *require.Assertions, op *opendal.Operator, fixture *f uuid.NewString(), )) - assert.Nil(op.Copy(sourcePath, targetPath)) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } @@ -149,89 +150,89 @@ func testCopyNested(assert *require.Assertions, op *opendal.Operator, fixture *f func testCopyOverwrite(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath, targetContent, _ := fixture.NewFile() assert.NotEqual(sourceContent, targetContent) - assert.Nil(op.Write(targetPath, targetContent)) + assert.Nil(op.Write(context.Background(), targetPath, targetContent)) - assert.Nil(op.Copy(sourcePath, targetPath)) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } func testCopyWithIfNotExistsToNewFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.NewFilePath() - assert.Nil(op.Copy(sourcePath, targetPath, opendal.CopyWithIfNotExists(true))) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithIfNotExists(true))) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } func testCopyWithIfNotExistsToExistingFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath, targetContent, _ := fixture.NewFile() - assert.Nil(op.Write(targetPath, targetContent)) + assert.Nil(op.Write(context.Background(), targetPath, targetContent)) - err := op.Copy(sourcePath, targetPath, opendal.CopyWithIfNotExists(true)) + err := op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithIfNotExists(true)) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(targetContent, bs, "target must not be overwritten") } func testCopyWithIfMatchMatch(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath, targetContent, _ := fixture.NewFile() assert.NotEqual(sourceContent, targetContent) - assert.Nil(op.Write(targetPath, targetContent)) + assert.Nil(op.Write(context.Background(), targetPath, targetContent)) - meta, err := op.Stat(targetPath) + meta, err := op.Stat(context.Background(), targetPath) assert.Nil(err, "stat must succeed") etag, ok := meta.ETag() assert.True(ok, "etag must exist") - assert.Nil(op.Copy(sourcePath, targetPath, opendal.CopyWithIfMatch(etag))) - bs, err := op.Read(targetPath) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithIfMatch(etag))) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } func testCopyWithIfMatchMismatch(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath, targetContent, _ := fixture.NewFile() assert.NotEqual(sourceContent, targetContent) - assert.Nil(op.Write(targetPath, targetContent)) + assert.Nil(op.Write(context.Background(), targetPath, targetContent)) - err := op.Copy(sourcePath, targetPath, opendal.CopyWithIfMatch("wrong-etag")) + err := op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithIfMatch("wrong-etag")) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(targetContent, bs, "target must not be overwritten") } func testCopyWithSourceVersionToNewFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) - meta, err := op.Stat(sourcePath) + meta, err := op.Stat(context.Background(), sourcePath) assert.Nil(err, "stat must succeed") version, ok := meta.Version() if !ok { @@ -239,21 +240,21 @@ func testCopyWithSourceVersionToNewFile(assert *require.Assertions, op *opendal. } newContent := genFixedBytes(uint(len(sourceContent)) + 1) - assert.Nil(op.Write(sourcePath, newContent), "overwrite must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, newContent), "overwrite must succeed") targetPath := fixture.NewFilePath() - assert.Nil(op.Copy(sourcePath, targetPath, opendal.CopyWithSourceVersion(version))) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithSourceVersion(version))) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } func testCopyWithSourceVersionToSameFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) - meta, err := op.Stat(sourcePath) + meta, err := op.Stat(context.Background(), sourcePath) assert.Nil(err, "stat must succeed") version, ok := meta.Version() if !ok { @@ -261,11 +262,11 @@ func testCopyWithSourceVersionToSameFile(assert *require.Assertions, op *opendal } newContent := genFixedBytes(uint(len(sourceContent)) + 1) - assert.Nil(op.Write(sourcePath, newContent), "overwrite must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, newContent), "overwrite must succeed") - assert.Nil(op.Copy(sourcePath, sourcePath, opendal.CopyWithSourceVersion(version))) + assert.Nil(op.Copy(context.Background(), sourcePath, sourcePath, opendal.CopyWithSourceVersion(version))) - bs, err := op.Read(sourcePath) + bs, err := op.Read(context.Background(), sourcePath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } @@ -290,12 +291,12 @@ func testCopyWithChunk(assert *require.Assertions, op *opendal.Operator, fixture sourcePath := fixture.NewFilePath() sourceContent := genFixedBytes(sourceSize) - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.NewFilePath() - assert.Nil(op.Copy(sourcePath, targetPath, opendal.CopyWithChunk(uint(chunk)))) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithChunk(uint(chunk)))) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } @@ -308,12 +309,12 @@ func testCopyWithChunkAndConcurrent(assert *require.Assertions, op *opendal.Oper sourcePath := fixture.NewFilePath() sourceContent := genFixedBytes(sourceSize) - assert.Nil(op.Write(sourcePath, sourceContent)) + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent)) targetPath := fixture.NewFilePath() - assert.Nil(op.Copy(sourcePath, targetPath, opendal.CopyWithChunk(uint(chunk)), opendal.CopyWithConcurrent(4))) + assert.Nil(op.Copy(context.Background(), sourcePath, targetPath, opendal.CopyWithChunk(uint(chunk)), opendal.CopyWithConcurrent(4))) - bs, err := op.Read(targetPath) + bs, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, bs) } diff --git a/bindings/go/tests/behavior_tests/create_dir_test.go b/bindings/go/tests/behavior_tests/create_dir_test.go index 8b0937b61266..5970cc7c05ec 100644 --- a/bindings/go/tests/behavior_tests/create_dir_test.go +++ b/bindings/go/tests/behavior_tests/create_dir_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "github.com/apache/opendal/bindings/go" "github.com/stretchr/testify/require" ) @@ -37,9 +38,9 @@ func testsCreateDir(cap *opendal.Capability) []behaviorTest { func testCreateDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path)) + assert.Nil(op.CreateDir(context.Background(), path)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.True(meta.IsDir()) } @@ -47,10 +48,10 @@ func testCreateDir(assert *require.Assertions, op *opendal.Operator, fixture *fi func testCreateDirExisting(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path)) - assert.Nil(op.CreateDir(path)) + assert.Nil(op.CreateDir(context.Background(), path)) + assert.Nil(op.CreateDir(context.Background(), path)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.True(meta.IsDir()) } diff --git a/bindings/go/tests/behavior_tests/delete_test.go b/bindings/go/tests/behavior_tests/delete_test.go index 9eabb83c682b..3d7e4331ead5 100644 --- a/bindings/go/tests/behavior_tests/delete_test.go +++ b/bindings/go/tests/behavior_tests/delete_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "fmt" "github.com/apache/opendal/bindings/go" @@ -49,11 +50,11 @@ func testsDelete(cap *opendal.Capability) []behaviorTest { func testDeleteFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - assert.Nil(op.Delete(path)) + assert.Nil(op.Delete(context.Background(), path)) - assert.False(op.IsExist(path)) + assert.False(op.IsExist(context.Background(), path)) } func testDeleteEmptyDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { @@ -63,26 +64,26 @@ func testDeleteEmptyDir(assert *require.Assertions, op *opendal.Operator, fixtur path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), path), "create must succeed") - assert.Nil(op.Delete(path)) + assert.Nil(op.Delete(context.Background(), path)) } func testDeleteWithSpecialChars(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := uuid.NewString() + " !@#$%^&()_+-=;',.txt" path, content, _ := fixture.NewFileWithPath(path) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - assert.Nil(op.Delete(path)) + assert.Nil(op.Delete(context.Background(), path)) - assert.False(op.IsExist(path)) + assert.False(op.IsExist(context.Background(), path)) } func testDeleteNotExisting(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := uuid.NewString() - assert.Nil(op.Delete(path)) + assert.Nil(op.Delete(context.Background(), path)) } func testDeleteWithRecursive(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { @@ -91,37 +92,37 @@ func testDeleteWithRecursive(assert *require.Assertions, op *opendal.Operator, f } dir := fixture.NewDirPath() - assert.Nil(op.CreateDir(dir), "create dir must succeed") + assert.Nil(op.CreateDir(context.Background(), dir), "create dir must succeed") // Write a few files under the directory. var filePaths []string for i := range 3 { path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%sfile-%d.txt", dir, i)) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") filePaths = append(filePaths, path) } - assert.Nil(op.Delete(dir, opendal.DeleteWithRecursive(true))) + assert.Nil(op.Delete(context.Background(), dir, opendal.DeleteWithRecursive(true))) - assert.False(op.IsExist(dir)) + assert.False(op.IsExist(context.Background(), dir)) for _, p := range filePaths { - assert.False(op.IsExist(p)) + assert.False(op.IsExist(context.Background(), p)) } } func testDeleteWithVersion(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) version, ok := meta.Version() if !ok { return } - assert.Nil(op.Delete(path, opendal.DeleteWithVersion(version))) + assert.Nil(op.Delete(context.Background(), path, opendal.DeleteWithVersion(version))) - assert.False(op.IsExist(path)) + assert.False(op.IsExist(context.Background(), path)) } diff --git a/bindings/go/tests/behavior_tests/list_test.go b/bindings/go/tests/behavior_tests/list_test.go index c681ba85607c..7eb2b30a2502 100644 --- a/bindings/go/tests/behavior_tests/list_test.go +++ b/bindings/go/tests/behavior_tests/list_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "fmt" "slices" "strings" @@ -64,16 +65,16 @@ func testsList(cap *opendal.Capability) []behaviorTest { } func testListCheck(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { - assert.Nil(op.Check(), "operator check must succeed") + assert.Nil(op.Check(context.Background()), "operator check must succeed") } func testListDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { parent := fixture.NewDirPath() path, content, size := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - obs, err := op.List(parent) + obs, err := op.List(context.Background(), parent) assert.Nil(err) defer obs.Close() @@ -85,7 +86,7 @@ func testListDir(assert *require.Assertions, op *opendal.Operator, fixture *fixt continue } - meta, err := op.Stat(entry.Path()) + meta, err := op.Stat(context.Background(), entry.Path()) assert.Nil(err) assert.True(meta.IsFile()) assert.Equal(uint64(size), meta.ContentLength()) @@ -98,16 +99,16 @@ func testListDir(assert *require.Assertions, op *opendal.Operator, fixture *fixt func testListRichDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { parent := fixture.NewDirPath() - assert.Nil(op.CreateDir(parent)) + assert.Nil(op.CreateDir(context.Background(), parent)) var expected []string for range 10 { path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) expected = append(expected, path) - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) } - obs, err := op.List(parent) + obs, err := op.List(context.Background(), parent) assert.Nil(err) defer obs.Close() var actual []string @@ -127,9 +128,9 @@ func testListRichDir(assert *require.Assertions, op *opendal.Operator, fixture * func testListEmptyDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { dir := fixture.NewDirPath() - assert.Nil(op.CreateDir(dir), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), dir), "create must succeed") - obs, err := op.List(dir) + obs, err := op.List(context.Background(), dir) assert.Nil(err) defer obs.Close() var paths []string @@ -142,14 +143,14 @@ func testListEmptyDir(assert *require.Assertions, op *opendal.Operator, fixture assert.Equal(1, len(paths), "dir should only return itself") paths = nil - obs, err = op.List(strings.TrimSuffix(dir, "/")) + obs, err = op.List(context.Background(), strings.TrimSuffix(dir, "/")) assert.Nil(err) defer obs.Close() for obs.Next() { entry := obs.Entry() path := entry.Path() paths = append(paths, path) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "given dir should exist") assert.True(meta.IsDir(), "given dir must be dir, but found: %v", path) } @@ -160,7 +161,7 @@ func testListEmptyDir(assert *require.Assertions, op *opendal.Operator, fixture func testListNonExistDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { dir := fixture.NewDirPath() - obs, err := op.List(dir) + obs, err := op.List(context.Background(), dir) assert.Nil(err) defer obs.Close() assert.False(obs.Next(), "dir should only return empty") @@ -169,9 +170,9 @@ func testListNonExistDir(assert *require.Assertions, op *opendal.Operator, fixtu func testListSubDir(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), path), "create must succeed") - obs, err := op.List("/") + obs, err := op.List(context.Background(), "/") assert.Nil(err) defer obs.Close() @@ -195,12 +196,12 @@ func testListNestedDir(assert *require.Assertions, op *opendal.Operator, fixture filePath := fixture.PushPath(fmt.Sprintf("%s%s", dir, uuid.NewString())) dirPath := fixture.PushPath(fmt.Sprintf("%s%s/", dir, uuid.NewString())) - assert.Nil(op.CreateDir(parent), "create must succeed") - assert.Nil(op.CreateDir(dir), "create must succeed") - assert.Nil(op.Write(filePath, []byte("test_list_nested_dir")), "write must succeed") - assert.Nil(op.CreateDir(dirPath), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), parent), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), dir), "create must succeed") + assert.Nil(op.Write(context.Background(), filePath, []byte("test_list_nested_dir")), "write must succeed") + assert.Nil(op.CreateDir(context.Background(), dirPath), "create must succeed") - obs, err := op.List(parent) + obs, err := op.List(context.Background(), parent) assert.Nil(err) defer obs.Close() var paths []string @@ -220,7 +221,7 @@ func testListNestedDir(assert *require.Assertions, op *opendal.Operator, fixture assert.Equal(foundParent, true, "parent should be found in list") assert.Equal(foundDir, true, "dir should be found in list") - obs, err = op.List(dir) + obs, err = op.List(context.Background(), dir) assert.Nil(err) defer obs.Close() paths = nil @@ -244,25 +245,25 @@ func testListNestedDir(assert *require.Assertions, op *opendal.Operator, fixture assert.True(foundDir, "dir should be found in list") assert.True(foundFile, "file should be found in list") - meta, err := op.Stat(filePath) + meta, err := op.Stat(context.Background(), filePath) assert.Nil(err) assert.True(meta.IsFile()) assert.Equal(uint64(20), meta.ContentLength()) assert.True(foundDirPath, "dir path should be found in list") - meta, err = op.Stat(dirPath) + meta, err = op.Stat(context.Background(), dirPath) assert.Nil(err) assert.True(meta.IsDir()) } func testListEntryMetadata(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { parent := fixture.NewDirPath() - assert.Nil(op.CreateDir(parent)) + assert.Nil(op.CreateDir(context.Background(), parent)) filePath, content, size := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(filePath, content)) + assert.Nil(op.Write(context.Background(), filePath, content)) - obs, err := op.List(parent) + obs, err := op.List(context.Background(), parent) assert.Nil(err) defer obs.Close() @@ -294,9 +295,9 @@ func testListDirWithFilePath(assert *require.Assertions, op *opendal.Operator, f parent := fixture.NewDirPath() path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - obs, err := op.List(strings.TrimSuffix(parent, "/")) + obs, err := op.List(context.Background(), strings.TrimSuffix(parent, "/")) assert.Nil(err) defer obs.Close() @@ -313,12 +314,12 @@ func testListWithDefaultOptions(assert *require.Assertions, op *opendal.Operator fileInParent, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) fileInSub := fixture.PushPath(fmt.Sprintf("%s%s", subDir, uuid.NewString())) - assert.Nil(op.CreateDir(parent)) - assert.Nil(op.CreateDir(subDir)) - assert.Nil(op.Write(fileInParent, content)) - assert.Nil(op.Write(fileInSub, content)) + assert.Nil(op.CreateDir(context.Background(), parent)) + assert.Nil(op.CreateDir(context.Background(), subDir)) + assert.Nil(op.Write(context.Background(), fileInParent, content)) + assert.Nil(op.Write(context.Background(), fileInSub, content)) - obs, err := op.List(parent) + obs, err := op.List(context.Background(), parent) assert.Nil(err) defer obs.Close() @@ -345,14 +346,14 @@ func testListWithRecursive(assert *require.Assertions, op *opendal.Operator, fix content := []byte("recursive test content") - assert.Nil(op.CreateDir(parent)) - assert.Nil(op.CreateDir(subDir)) - assert.Nil(op.CreateDir(deepDir)) - assert.Nil(op.Write(fileTop, content)) - assert.Nil(op.Write(fileMid, content)) - assert.Nil(op.Write(fileDeep, content)) + assert.Nil(op.CreateDir(context.Background(), parent)) + assert.Nil(op.CreateDir(context.Background(), subDir)) + assert.Nil(op.CreateDir(context.Background(), deepDir)) + assert.Nil(op.Write(context.Background(), fileTop, content)) + assert.Nil(op.Write(context.Background(), fileMid, content)) + assert.Nil(op.Write(context.Background(), fileDeep, content)) - obs, err := op.List(parent, opendal.ListWithRecursive(true)) + obs, err := op.List(context.Background(), parent, opendal.ListWithRecursive(true)) assert.Nil(err) defer obs.Close() @@ -369,16 +370,16 @@ func testListWithRecursive(assert *require.Assertions, op *opendal.Operator, fix func testListWithLimit(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { parent := fixture.NewDirPath() - assert.Nil(op.CreateDir(parent)) + assert.Nil(op.CreateDir(context.Background(), parent)) // Write 5 files. for range 5 { path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) } // List with limit=2; the operation must succeed without error. - obs, err := op.List(parent, opendal.ListWithLimit(2)) + obs, err := op.List(context.Background(), parent, opendal.ListWithLimit(2)) assert.Nil(err) defer obs.Close() @@ -393,20 +394,20 @@ func testListWithLimit(assert *require.Assertions, op *opendal.Operator, fixture func testListWithStartAfter(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { parent := fixture.NewDirPath() - assert.Nil(op.CreateDir(parent)) + assert.Nil(op.CreateDir(context.Background(), parent)) // Write files with predictable sorted names. var filePaths []string for i := range 5 { name := fmt.Sprintf("%sfile-%02d", parent, i) filePaths = append(filePaths, name) - assert.Nil(op.Write(name, []byte("content"))) + assert.Nil(op.Write(context.Background(), name, []byte("content"))) } slices.Sort(filePaths) // Start listing from the second file (index 1). pivotName := strings.TrimPrefix(filePaths[1], "/") - obs, err := op.List(parent, opendal.ListWithStartAfter(pivotName)) + obs, err := op.List(context.Background(), parent, opendal.ListWithStartAfter(pivotName)) assert.Nil(err) defer obs.Close() @@ -426,10 +427,10 @@ func testListWithVersions(assert *require.Assertions, op *opendal.Operator, fixt parent := fixture.NewDirPath() path, _, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, []byte("version-1")), "first write must succeed") - assert.Nil(op.Write(path, []byte("version-2")), "second write must succeed") + assert.Nil(op.Write(context.Background(), path, []byte("version-1")), "first write must succeed") + assert.Nil(op.Write(context.Background(), path, []byte("version-2")), "second write must succeed") - obs, err := op.List(path, opendal.ListWithVersions(true)) + obs, err := op.List(context.Background(), path, opendal.ListWithVersions(true)) assert.Nil(err, "list with versions must succeed") defer obs.Close() @@ -457,9 +458,9 @@ func testListWithDeleted(assert *require.Assertions, op *opendal.Operator, fixtu parent := fixture.NewDirPath() path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - obs, err := op.List(path, opendal.ListWithDeleted(true)) + obs, err := op.List(context.Background(), path, opendal.ListWithDeleted(true)) assert.Nil(err, "list with deleted must succeed before deletion") defer obs.Close() var beforeCount int @@ -471,9 +472,9 @@ func testListWithDeleted(assert *require.Assertions, op *opendal.Operator, fixtu assert.Nil(obs.Error()) assert.Equal(1, beforeCount, "active file must appear exactly once before deletion") - assert.Nil(op.Delete(path), "delete must succeed") + assert.Nil(op.Delete(context.Background(), path), "delete must succeed") - obs2, err := op.List(path, opendal.ListWithDeleted(true)) + obs2, err := op.List(context.Background(), path, opendal.ListWithDeleted(true)) assert.Nil(err, "list with deleted must succeed after deletion") defer obs2.Close() var foundDeleteMarker bool diff --git a/bindings/go/tests/behavior_tests/opendal_test.go b/bindings/go/tests/behavior_tests/opendal_test.go index 3c7cafab7b96..9cab639e5a7f 100644 --- a/bindings/go/tests/behavior_tests/opendal_test.go +++ b/bindings/go/tests/behavior_tests/opendal_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "crypto/rand" "fmt" "math/big" @@ -283,7 +284,7 @@ func (f *fixture) Cleanup(assert *require.Assertions) { defer f.lock.Unlock() for i := len(f.paths) - 1; i >= 0; i-- { - f.op.Delete(f.paths[i]) + f.op.Delete(context.Background(), f.paths[i]) } } diff --git a/bindings/go/tests/behavior_tests/presign_test.go b/bindings/go/tests/behavior_tests/presign_test.go index a8f8fd13c6a6..4dcb6c075bdf 100644 --- a/bindings/go/tests/behavior_tests/presign_test.go +++ b/bindings/go/tests/behavior_tests/presign_test.go @@ -21,6 +21,7 @@ package opendal_test import ( "bytes" + "context" "io" "net/http" "strconv" @@ -54,7 +55,7 @@ func testsPresign(cap *opendal.Capability) []behaviorTest { func testPresignWrite(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - req, err := op.PresignWrite(path, time.Hour) + req, err := op.PresignWrite(context.Background(), path, time.Hour) assert.Nil(err) req.ContentLength = int64(len(content)) @@ -68,7 +69,7 @@ func testPresignWrite(assert *require.Assertions, op *opendal.Operator, fixture assert.GreaterOrEqual(resp.StatusCode, 200) assert.Less(resp.StatusCode, 300) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.EqualValues(size, meta.ContentLength()) } @@ -76,9 +77,9 @@ func testPresignWrite(assert *require.Assertions, op *opendal.Operator, fixture func testPresignRead(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - req, err := op.PresignRead(path, time.Hour) + req, err := op.PresignRead(context.Background(), path, time.Hour) assert.Nil(err) resp, err := http.DefaultClient.Do(req) @@ -95,9 +96,9 @@ func testPresignRead(assert *require.Assertions, op *opendal.Operator, fixture * func testPresignStat(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - req, err := op.PresignStat(path, time.Hour) + req, err := op.PresignStat(context.Background(), path, time.Hour) assert.Nil(err) resp, err := http.DefaultClient.Do(req) @@ -115,9 +116,9 @@ func testPresignStat(assert *require.Assertions, op *opendal.Operator, fixture * func testPresignDelete(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - req, err := op.PresignDelete(path, time.Hour) + req, err := op.PresignDelete(context.Background(), path, time.Hour) assert.Nil(err) resp, err := http.DefaultClient.Do(req) @@ -128,7 +129,7 @@ func testPresignDelete(assert *require.Assertions, op *opendal.Operator, fixture assert.GreaterOrEqual(resp.StatusCode, 200) assert.Less(resp.StatusCode, 300) - exists, err := op.IsExist(path) + exists, err := op.IsExist(context.Background(), path) assert.Nil(err) assert.False(exists) } diff --git a/bindings/go/tests/behavior_tests/read_test.go b/bindings/go/tests/behavior_tests/read_test.go index 5482bbf67e4c..61c0d102fc79 100644 --- a/bindings/go/tests/behavior_tests/read_test.go +++ b/bindings/go/tests/behavior_tests/read_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "io" "time" @@ -69,9 +70,9 @@ func testsRead(cap *opendal.Capability) []behaviorTest { func testReadWithConcurrentChunkGap(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - bs, err := op.Read(path, + bs, err := op.Read(context.Background(), path, opendal.ReadWithConcurrent(2), opendal.ReadWithChunk(1024*1024), opendal.ReadWithGap(4096), @@ -86,9 +87,9 @@ func testReadWithWriteOptions(assert *require.Assertions, op *opendal.Operator, content := genFixedBytes(1024 * 1024) offset, length := genOffsetLength(uint(len(content))) - assert.Nil(op.Write(path, content, opendal.WriteWithChunk(256*1024), opendal.WriteWithConcurrent(2))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithChunk(256*1024), opendal.WriteWithConcurrent(2))) - bs, err := op.Read(path, + bs, err := op.Read(context.Background(), path, opendal.ReadWithRange(uint64(offset), uint64(length)), opendal.ReadWithConcurrent(2), opendal.ReadWithChunk(128*1024), @@ -102,17 +103,17 @@ func testReadWithWriteOptions(assert *require.Assertions, op *opendal.Operator, func testReadWithIfModifiedSince(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) lastModified := meta.LastModified() - bs, err := op.Read(path, opendal.ReadWithIfModifiedSince(lastModified.Add(-time.Second))) + bs, err := op.Read(context.Background(), path, opendal.ReadWithIfModifiedSince(lastModified.Add(-time.Second))) assert.Nil(err, "read with if-modified-since before last modified must succeed") assert.Equal(content, bs, "read content") - _, err = op.Read(path, opendal.ReadWithIfModifiedSince(lastModified.Add(time.Second))) + _, err = op.Read(context.Background(), path, opendal.ReadWithIfModifiedSince(lastModified.Add(time.Second))) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) } @@ -120,17 +121,17 @@ func testReadWithIfModifiedSince(assert *require.Assertions, op *opendal.Operato func testReadWithIfUnmodifiedSince(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) lastModified := meta.LastModified() - bs, err := op.Read(path, opendal.ReadWithIfUnmodifiedSince(lastModified.Add(time.Second))) + bs, err := op.Read(context.Background(), path, opendal.ReadWithIfUnmodifiedSince(lastModified.Add(time.Second))) assert.Nil(err, "read with if-unmodified-since after last modified must succeed") assert.Equal(content, bs, "read content") - _, err = op.Read(path, opendal.ReadWithIfUnmodifiedSince(lastModified.Add(-time.Second))) + _, err = op.Read(context.Background(), path, opendal.ReadWithIfUnmodifiedSince(lastModified.Add(-time.Second))) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) } @@ -138,22 +139,22 @@ func testReadWithIfUnmodifiedSince(assert *require.Assertions, op *opendal.Opera func testReadWithVersion(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) version, ok := meta.Version() if !ok { return } - data, err := op.Read(path, opendal.ReadWithVersion(version)) + data, err := op.Read(context.Background(), path, opendal.ReadWithVersion(version)) assert.Nil(err) assert.Equal(content, data, "read content") // After overwriting, the previous version data is still readable. - assert.Nil(op.Write(path, []byte("1")), "overwrite must succeed") - second, err := op.Read(path, opendal.ReadWithVersion(version)) + assert.Nil(op.Write(context.Background(), path, []byte("1")), "overwrite must succeed") + second, err := op.Read(context.Background(), path, opendal.ReadWithVersion(version)) assert.Nil(err) assert.Equal(content, second, "read old version content") } @@ -162,9 +163,9 @@ func testReadWithRange(assert *require.Assertions, op *opendal.Operator, fixture path, content, size := fixture.NewFile() offset, length := genOffsetLength(size) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - bs, err := op.Read(path, opendal.ReadWithRange(uint64(offset), uint64(length))) + bs, err := op.Read(context.Background(), path, opendal.ReadWithRange(uint64(offset), uint64(length))) assert.Nil(err) assert.Equal(length, int64(len(bs)), "read range size") assert.Equal(content[offset:offset+length], bs, "read range content") @@ -207,20 +208,20 @@ func testReadWithContentLengthHint(assert *require.Assertions, op *opendal.Opera func testReadWithIfMatch(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) etag, ok := meta.ETag() if !ok { return } - _, err = op.Read(path, opendal.ReadWithIfMatch("\"invalid_etag\"")) + _, err = op.Read(context.Background(), path, opendal.ReadWithIfMatch("\"invalid_etag\"")) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(path, opendal.ReadWithIfMatch(etag)) + bs, err := op.Read(context.Background(), path, opendal.ReadWithIfMatch(etag)) assert.Nil(err, "read with matching etag must succeed") assert.Equal(content, bs, "read content") } @@ -228,20 +229,20 @@ func testReadWithIfMatch(assert *require.Assertions, op *opendal.Operator, fixtu func testReadWithIfNoneMatch(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) etag, ok := meta.ETag() if !ok { return } - _, err = op.Read(path, opendal.ReadWithIfNoneMatch(etag)) + _, err = op.Read(context.Background(), path, opendal.ReadWithIfNoneMatch(etag)) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(path, opendal.ReadWithIfNoneMatch("\"invalid_etag\"")) + bs, err := op.Read(context.Background(), path, opendal.ReadWithIfNoneMatch("\"invalid_etag\"")) assert.Nil(err, "read with non-matching etag must succeed") assert.Equal(content, bs, "read content") } @@ -249,9 +250,9 @@ func testReadWithIfNoneMatch(assert *require.Assertions, op *opendal.Operator, f func testReadFull(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err) assert.Equal(size, uint(len(bs)), "read size") assert.Equal(content, bs, "read content") @@ -260,9 +261,9 @@ func testReadFull(assert *require.Assertions, op *opendal.Operator, fixture *fix func testReader(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - r, err := op.Reader(path) + r, err := op.Reader(context.Background(), path) assert.Nil(err) defer r.Close() bs := make([]byte, size) @@ -275,7 +276,7 @@ func testReader(assert *require.Assertions, op *opendal.Operator, fixture *fixtu func testReadNotExist(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewFilePath() - _, err := op.Read(path) + _, err := op.Read(context.Background(), path) assert.NotNil(err) assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } @@ -287,9 +288,9 @@ func testReadWithDirPath(assert *require.Assertions, op *opendal.Operator, fixtu path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), path), "create must succeed") - _, err := op.Read(path) + _, err := op.Read(context.Background(), path) assert.NotNil(err) assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -297,9 +298,9 @@ func testReadWithDirPath(assert *require.Assertions, op *opendal.Operator, fixtu func testReadWithSpecialChars(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFileWithPath(uuid.NewString() + " !@#$%^&()_+-=;',.txt") - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err) assert.Equal(size, uint(len(bs))) assert.Equal(content, bs) @@ -308,14 +309,14 @@ func testReadWithSpecialChars(assert *require.Assertions, op *opendal.Operator, func testIOCopy(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - r, err := op.Reader(path) + r, err := op.Reader(context.Background(), path) assert.Nil(err) pathCopy := fixture.NewFilePath() - w, err := op.Writer(pathCopy) + w, err := op.Writer(context.Background(), pathCopy) assert.Nil(err) n, err := io.Copy(w, r) @@ -325,7 +326,7 @@ func testIOCopy(assert *require.Assertions, op *opendal.Operator, fixture *fixtu assert.Nil(r.Close(), "close reader must succeed") assert.Nil(w.Close(), "close writer must succeed") - copyContent, err := op.Read(pathCopy) + copyContent, err := op.Read(context.Background(), pathCopy) assert.Nil(err) assert.Equal(size, uint(len(copyContent)), "read size") assert.Equal(content, copyContent, "read content") @@ -335,9 +336,9 @@ func testReaderSeek(assert *require.Assertions, op *opendal.Operator, fixture *f path, content, size := fixture.NewFile() offset, length := genOffsetLength(size) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - r, err := op.Reader(path) + r, err := op.Reader(context.Background(), path) assert.Nil(err) defer r.Close() diff --git a/bindings/go/tests/behavior_tests/rename_test.go b/bindings/go/tests/behavior_tests/rename_test.go index 3a71e1b578e2..7ef58f289111 100644 --- a/bindings/go/tests/behavior_tests/rename_test.go +++ b/bindings/go/tests/behavior_tests/rename_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "fmt" "github.com/apache/opendal/bindings/go" @@ -45,17 +46,17 @@ func testsRename(cap *opendal.Capability) []behaviorTest { func testRenameFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent), "write must succeed") targetPath := fixture.NewFilePath() - assert.Nil(op.Rename(sourcePath, targetPath)) + assert.Nil(op.Rename(context.Background(), sourcePath, targetPath)) - _, err := op.Stat(sourcePath) + _, err := op.Stat(context.Background(), sourcePath) assert.NotNil(err, "stat must fail") assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err) assert.Equal(sourceContent, targetContent) } @@ -64,7 +65,7 @@ func testRenameNonExistingSource(assert *require.Assertions, op *opendal.Operato sourcePath := fixture.NewFilePath() targetPath := fixture.NewFilePath() - err := op.Rename(sourcePath, targetPath) + err := op.Rename(context.Background(), sourcePath, targetPath) assert.NotNil(err, "rename must fail") assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } @@ -77,9 +78,9 @@ func testRenameSourceDir(assert *require.Assertions, op *opendal.Operator, fixtu sourcePath := fixture.NewDirPath() targetPth := fixture.NewFilePath() - assert.Nil(op.CreateDir(sourcePath), "create must succeed") + assert.Nil(op.CreateDir(context.Background(), sourcePath), "create must succeed") - err := op.Rename(sourcePath, targetPth) + err := op.Rename(context.Background(), sourcePath, targetPth) assert.NotNil(err, "rename must fail") assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -91,12 +92,12 @@ func testRenameTargetDir(assert *require.Assertions, op *opendal.Operator, fixtu sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent), "write must succeed") targetPath := fixture.NewDirPath() - assert.Nil(op.CreateDir(targetPath)) + assert.Nil(op.CreateDir(context.Background(), targetPath)) - err := op.Rename(sourcePath, targetPath) + err := op.Rename(context.Background(), sourcePath, targetPath) assert.NotNil(err) assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -104,9 +105,9 @@ func testRenameTargetDir(assert *require.Assertions, op *opendal.Operator, fixtu func testRenameSelf(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent), "write must succeed") - err := op.Rename(sourcePath, sourcePath) + err := op.Rename(context.Background(), sourcePath, sourcePath) assert.NotNil(err) assert.Equal(opendal.CodeIsSameFile, assertErrorCode(err)) } @@ -114,7 +115,7 @@ func testRenameSelf(assert *require.Assertions, op *opendal.Operator, fixture *f func testRenameNested(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent), "write must succeed") targetPath := fixture.PushPath(fmt.Sprintf( "%s/%s/%s", @@ -123,13 +124,13 @@ func testRenameNested(assert *require.Assertions, op *opendal.Operator, fixture uuid.NewString(), )) - assert.Nil(op.Rename(sourcePath, targetPath)) + assert.Nil(op.Rename(context.Background(), sourcePath, targetPath)) - _, err := op.Stat(sourcePath) + _, err := op.Stat(context.Background(), sourcePath) assert.NotNil(err, "stat must fail") assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) - targetContent, err := op.Read(targetPath) + targetContent, err := op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } @@ -137,20 +138,20 @@ func testRenameNested(assert *require.Assertions, op *opendal.Operator, fixture func testRenameOverwrite(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { sourcePath, sourceContent, _ := fixture.NewFile() - assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed") + assert.Nil(op.Write(context.Background(), sourcePath, sourceContent), "write must succeed") targetPath, targetContent, _ := fixture.NewFile() assert.NotEqual(sourceContent, targetContent) - assert.Nil(op.Write(targetPath, targetContent), "write must succeed") + assert.Nil(op.Write(context.Background(), targetPath, targetContent), "write must succeed") - assert.Nil(op.Rename(sourcePath, targetPath)) + assert.Nil(op.Rename(context.Background(), sourcePath, targetPath)) - _, err := op.Stat(sourcePath) + _, err := op.Stat(context.Background(), sourcePath) assert.NotNil(err, "stat must fail") assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) - targetContent, err = op.Read(targetPath) + targetContent, err = op.Read(context.Background(), targetPath) assert.Nil(err, "read must succeed") assert.Equal(sourceContent, targetContent) } diff --git a/bindings/go/tests/behavior_tests/stat_test.go b/bindings/go/tests/behavior_tests/stat_test.go index 1af3891128c4..b6a47468b6f6 100644 --- a/bindings/go/tests/behavior_tests/stat_test.go +++ b/bindings/go/tests/behavior_tests/stat_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" "fmt" "strings" "time" @@ -71,15 +72,15 @@ func assertOptionalMetaString(assert *require.Assertions, name string, accessor func testStatFile(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.True(meta.IsFile()) assert.Equal(meta.ContentLength(), uint64(size)) if op.Info().GetFullCapability().CreateDir() { - _, err := op.Stat(fmt.Sprintf("%s/", path)) + _, err := op.Stat(context.Background(), fmt.Sprintf("%s/", path)) assert.NotNil(err) assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } @@ -91,13 +92,13 @@ func testStatDir(assert *require.Assertions, op *opendal.Operator, fixture *fixt } path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path)) + assert.Nil(op.CreateDir(context.Background(), path)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.True(meta.IsDir()) - meta, err = op.Stat(strings.TrimSuffix(path, "/")) + meta, err = op.Stat(context.Background(), strings.TrimSuffix(path, "/")) if err != nil { assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } else { @@ -113,9 +114,9 @@ func testStatNestedParentDir(assert *require.Assertions, op *opendal.Operator, f parent := fixture.NewDirPath() path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, uuid.NewString())) - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(parent) + meta, err := op.Stat(context.Background(), parent) assert.Nil(err) assert.True(meta.IsDir()) } @@ -123,9 +124,9 @@ func testStatNestedParentDir(assert *require.Assertions, op *opendal.Operator, f func testStatWithSpecialChars(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFileWithPath(uuid.NewString() + " !@#$%^&()_+-=;',.txt") - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err) assert.True(meta.IsFile()) assert.Equal(uint64(size), meta.ContentLength()) @@ -134,9 +135,9 @@ func testStatWithSpecialChars(assert *require.Assertions, op *opendal.Operator, func testStatNotCleanedPath(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(fmt.Sprintf("//%s", path)) + meta, err := op.Stat(context.Background(), fmt.Sprintf("//%s", path)) assert.Nil(err) assert.True(meta.IsFile()) assert.Equal(uint64(size), meta.ContentLength()) @@ -145,23 +146,23 @@ func testStatNotCleanedPath(assert *require.Assertions, op *opendal.Operator, fi func testStatNotExist(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewFilePath() - _, err := op.Stat(path) + _, err := op.Stat(context.Background(), path) assert.NotNil(err) assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) if op.Info().GetFullCapability().CreateDir() { - _, err := op.Stat(fmt.Sprintf("%s/", path)) + _, err := op.Stat(context.Background(), fmt.Sprintf("%s/", path)) assert.NotNil(err) assert.Equal(opendal.CodeNotFound, assertErrorCode(err)) } } func testStatRoot(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { - meta, err := op.Stat("") + meta, err := op.Stat(context.Background(), "") assert.Nil(err) assert.True(meta.IsDir()) - meta, err = op.Stat("/") + meta, err = op.Stat(context.Background(), "/") assert.Nil(err) assert.True(meta.IsDir()) @@ -200,12 +201,12 @@ func testStatFileMetadata(assert *require.Assertions, op *opendal.Operator, fixt before := time.Now().Add(-time.Hour) if len(writeOpts) == 0 { - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") } else { - assert.Nil(op.Write(path, content, writeOpts...), "write with metadata must succeed") + assert.Nil(op.Write(context.Background(), path, content, writeOpts...), "write with metadata must succeed") } - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.True(meta.IsFile(), "written object must be a file") @@ -274,20 +275,20 @@ func testStatWithIfMatch(assert *require.Assertions, op *opendal.Operator, fixtu if isCapEnabled(cap.WriteWithContentType, "write_with_content_type") { writeOpts = append(writeOpts, opendal.WriteWithContentType("text/plain")) } - assert.Nil(op.Write(path, content, writeOpts...), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content, writeOpts...), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") etag, ok := meta.ETag() assert.True(ok, "etag must exist") // Stat with a matching ETag must succeed. - meta, err = op.Stat(path, opendal.StatWithIfMatch(etag)) + meta, err = op.Stat(context.Background(), path, opendal.StatWithIfMatch(etag)) assert.Nil(err, "stat with matching if_match must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) // Stat with a non-matching ETag must fail with ConditionNotMatch. - _, err = op.Stat(path, opendal.StatWithIfMatch("\"invalid_etag\"")) + _, err = op.Stat(context.Background(), path, opendal.StatWithIfMatch("\"invalid_etag\"")) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) } @@ -300,20 +301,20 @@ func testStatWithIfNoneMatch(assert *require.Assertions, op *opendal.Operator, f path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") etag, ok := meta.ETag() assert.True(ok, "etag must exist") // Stat with a non-matching ETag must succeed and return metadata. - meta, err = op.Stat(path, opendal.StatWithIfNoneMatch("\"invalid_etag\"")) + meta, err = op.Stat(context.Background(), path, opendal.StatWithIfNoneMatch("\"invalid_etag\"")) assert.Nil(err, "stat with non-matching if_none_match must succeed") assert.Equal(uint64(size), meta.ContentLength()) // Stat with a matching ETag must fail with ConditionNotMatch. - _, err = op.Stat(path, opendal.StatWithIfNoneMatch(etag)) + _, err = op.Stat(context.Background(), path, opendal.StatWithIfNoneMatch(etag)) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) } @@ -326,9 +327,9 @@ func testStatWithIfModifiedSince(assert *require.Assertions, op *opendal.Operato path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.True(meta.IsFile(), "written object must be a file") assert.Equal(uint64(len(content)), meta.ContentLength()) @@ -336,11 +337,11 @@ func testStatWithIfModifiedSince(assert *require.Assertions, op *opendal.Operato lastModified := meta.LastModified() assert.False(lastModified.IsZero(), "last_modified must exist") - meta, err = op.Stat(path, opendal.StatWithIfModifiedSince(lastModified.Add(-time.Second))) + meta, err = op.Stat(context.Background(), path, opendal.StatWithIfModifiedSince(lastModified.Add(-time.Second))) assert.Nil(err, "stat with older if_modified_since must succeed") assert.Equal(lastModified, meta.LastModified()) - _, err = op.Stat(path, opendal.StatWithIfModifiedSince(lastModified.Add(time.Second))) + _, err = op.Stat(context.Background(), path, opendal.StatWithIfModifiedSince(lastModified.Add(time.Second))) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) } @@ -353,9 +354,9 @@ func testStatWithIfUnmodifiedSince(assert *require.Assertions, op *opendal.Opera path, content, _ := fixture.NewFile() - assert.Nil(op.Write(path, content), "write must succeed") + assert.Nil(op.Write(context.Background(), path, content), "write must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.True(meta.IsFile(), "written object must be a file") assert.Equal(uint64(len(content)), meta.ContentLength()) @@ -363,11 +364,11 @@ func testStatWithIfUnmodifiedSince(assert *require.Assertions, op *opendal.Opera lastModified := meta.LastModified() assert.False(lastModified.IsZero(), "last_modified must exist") - _, err = op.Stat(path, opendal.StatWithIfUnmodifiedSince(lastModified.Add(-time.Second))) + _, err = op.Stat(context.Background(), path, opendal.StatWithIfUnmodifiedSince(lastModified.Add(-time.Second))) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - meta, err = op.Stat(path, opendal.StatWithIfUnmodifiedSince(lastModified.Add(time.Second))) + meta, err = op.Stat(context.Background(), path, opendal.StatWithIfUnmodifiedSince(lastModified.Add(time.Second))) assert.Nil(err, "stat with newer if_unmodified_since must succeed") assert.Equal(lastModified, meta.LastModified()) } @@ -378,9 +379,9 @@ func testStatDirMetadata(assert *require.Assertions, op *opendal.Operator, fixtu } path := fixture.NewDirPath() - assert.Nil(op.CreateDir(path), "create dir must succeed") + assert.Nil(op.CreateDir(context.Background(), path), "create dir must succeed") - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.True(meta.IsDir(), "created object must be a dir") diff --git a/bindings/go/tests/behavior_tests/write_test.go b/bindings/go/tests/behavior_tests/write_test.go index 9e866dfe4e20..6a5d88901bdb 100644 --- a/bindings/go/tests/behavior_tests/write_test.go +++ b/bindings/go/tests/behavior_tests/write_test.go @@ -20,6 +20,7 @@ package opendal_test import ( + "context" opendal "github.com/apache/opendal/bindings/go" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -52,9 +53,9 @@ func testsWrite(cap *opendal.Capability) []behaviorTest { func testWriteOnly(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFile() - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(size), meta.ContentLength()) } @@ -65,9 +66,9 @@ func testWriteWithEmptyContent(assert *require.Assertions, op *opendal.Operator, } path := fixture.NewFilePath() - assert.Nil(op.Write(path, []byte{})) + assert.Nil(op.Write(context.Background(), path, []byte{})) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(0), meta.ContentLength()) } @@ -75,7 +76,7 @@ func testWriteWithEmptyContent(assert *require.Assertions, op *opendal.Operator, func testWriteWithDirPath(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path := fixture.NewDirPath() - err := op.Write(path, []byte("1")) + err := op.Write(context.Background(), path, []byte("1")) assert.NotNil(err) assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err)) } @@ -83,9 +84,9 @@ func testWriteWithDirPath(assert *require.Assertions, op *opendal.Operator, fixt func testWriteWithSpecialChars(assert *require.Assertions, op *opendal.Operator, fixture *fixture) { path, content, size := fixture.NewFileWithPath(uuid.NewString() + " !@#$%^&()_+-=;',.txt") - assert.Nil(op.Write(path, content)) + assert.Nil(op.Write(context.Background(), path, content)) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(size), meta.ContentLength()) } @@ -99,13 +100,13 @@ func testWriteOverwrite(assert *require.Assertions, op *opendal.Operator, fixtur size := uint(5 * 1024 * 1024) contentOne, contentTwo := genFixedBytes(size), genFixedBytes(size) - assert.Nil(op.Write(path, contentOne)) - bs, err := op.Read(path) + assert.Nil(op.Write(context.Background(), path, contentOne)) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal(contentOne, bs, "read content_one") - assert.Nil(op.Write(path, contentTwo)) - bs, err = op.Read(path) + assert.Nil(op.Write(context.Background(), path, contentTwo)) + bs, err = op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.NotEqual(contentOne, bs, "content_one must be overwrote") assert.Equal(contentTwo, bs, "read content_two") @@ -118,9 +119,9 @@ func testWriteWithCacheControl(assert *require.Assertions, op *opendal.Operator, path := fixture.NewFilePath() content := []byte("hello") - assert.Nil(op.Write(path, content, opendal.WriteWithCacheControl("max-age=60"))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithCacheControl("max-age=60"))) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) cacheControl, ok := meta.CacheControl() @@ -135,9 +136,9 @@ func testWriteWithContentType(assert *require.Assertions, op *opendal.Operator, path := fixture.NewFilePath() content := []byte("hello") - assert.Nil(op.Write(path, content, opendal.WriteWithContentType("text/plain"))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithContentType("text/plain"))) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) contentType, ok := meta.ContentType() @@ -152,9 +153,9 @@ func testWriteWithContentDisposition(assert *require.Assertions, op *opendal.Ope path := fixture.NewFilePath() content := []byte("hello") - assert.Nil(op.Write(path, content, opendal.WriteWithContentDisposition("attachment; filename=hello.txt"))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithContentDisposition("attachment; filename=hello.txt"))) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) contentDisposition, ok := meta.ContentDisposition() @@ -169,9 +170,9 @@ func testWriteWithContentEncoding(assert *require.Assertions, op *opendal.Operat path := fixture.NewFilePath() content := []byte("hello") - assert.Nil(op.Write(path, content, opendal.WriteWithContentEncoding("gzip"))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithContentEncoding("gzip"))) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) contentEncoding, ok := meta.ContentEncoding() @@ -186,12 +187,12 @@ func testWriteWithUserMetadata(assert *require.Assertions, op *opendal.Operator, path := fixture.NewFilePath() content := []byte("hello") - assert.Nil(op.Write(path, content, opendal.WriteWithUserMetadata(map[string]string{ + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithUserMetadata(map[string]string{ "language": "go", "project": "opendal", }))) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(len(content)), meta.ContentLength()) assert.Equal(map[string]string{ @@ -206,14 +207,14 @@ func testWriteWithIfMatch(assert *require.Assertions, op *opendal.Operator, fixt } path := fixture.NewFilePath() - assert.Nil(op.Write(path, []byte("hello"))) - meta, err := op.Stat(path) + assert.Nil(op.Write(context.Background(), path, []byte("hello"))) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") etag, ok := meta.ETag() assert.True(ok, "etag must exist") - assert.Nil(op.Write(path, []byte("world"), opendal.WriteWithIfMatch(etag))) - bs, err := op.Read(path) + assert.Nil(op.Write(context.Background(), path, []byte("world"), opendal.WriteWithIfMatch(etag))) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal([]byte("world"), bs) } @@ -224,17 +225,17 @@ func testWriteWithIfNoneMatch(assert *require.Assertions, op *opendal.Operator, } path := fixture.NewFilePath() - assert.Nil(op.Write(path, []byte("hello"))) - meta, err := op.Stat(path) + assert.Nil(op.Write(context.Background(), path, []byte("hello"))) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") etag, ok := meta.ETag() assert.True(ok, "etag must exist") - err = op.Write(path, []byte("world"), opendal.WriteWithIfNoneMatch(etag)) + err = op.Write(context.Background(), path, []byte("world"), opendal.WriteWithIfNoneMatch(etag)) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal([]byte("hello"), bs) } @@ -245,12 +246,12 @@ func testWriteWithIfNotExists(assert *require.Assertions, op *opendal.Operator, } path := fixture.NewFilePath() - assert.Nil(op.Write(path, []byte("hello"), opendal.WriteWithIfNotExists(true))) - err := op.Write(path, []byte("world"), opendal.WriteWithIfNotExists(true)) + assert.Nil(op.Write(context.Background(), path, []byte("hello"), opendal.WriteWithIfNotExists(true))) + err := op.Write(context.Background(), path, []byte("world"), opendal.WriteWithIfNotExists(true)) assert.NotNil(err) assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err)) - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal([]byte("hello"), bs) } @@ -265,7 +266,7 @@ func testWriterWrite(assert *require.Assertions, op *opendal.Operator, fixture * contentA := genFixedBytes(size) contentB := genFixedBytes(size) - w, err := op.Writer(path) + w, err := op.Writer(context.Background(), path) assert.Nil(err) _, err = w.Write(contentA) assert.Nil(err) @@ -273,11 +274,11 @@ func testWriterWrite(assert *require.Assertions, op *opendal.Operator, fixture * assert.Nil(err) assert.Nil(w.Close()) - meta, err := op.Stat(path) + meta, err := op.Stat(context.Background(), path) assert.Nil(err, "stat must succeed") assert.Equal(uint64(size*2), meta.ContentLength()) - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal(uint64(size*2), uint64(len(bs)), "read size") assert.Equal(contentA, bs[:size], "read contentA") @@ -291,9 +292,9 @@ func testWriteWithChunkAndConcurrent(assert *require.Assertions, op *opendal.Ope path := fixture.NewFilePath() content := genFixedBytes(1024 * 1024) - assert.Nil(op.Write(path, content, opendal.WriteWithChunk(256*1024), opendal.WriteWithConcurrent(2))) + assert.Nil(op.Write(context.Background(), path, content, opendal.WriteWithChunk(256*1024), opendal.WriteWithConcurrent(2))) - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal(content, bs) } @@ -305,19 +306,19 @@ func testWriterWithAppend(assert *require.Assertions, op *opendal.Operator, fixt path := fixture.NewFilePath() - w, err := op.Writer(path, opendal.WriteWithAppend(true)) + w, err := op.Writer(context.Background(), path, opendal.WriteWithAppend(true)) assert.Nil(err) _, err = w.Write([]byte("hello")) assert.Nil(err) assert.Nil(w.Close()) - w, err = op.Writer(path, opendal.WriteWithAppend(true)) + w, err = op.Writer(context.Background(), path, opendal.WriteWithAppend(true)) assert.Nil(err) _, err = w.Write([]byte(" world")) assert.Nil(err) assert.Nil(w.Close()) - bs, err := op.Read(path) + bs, err := op.Read(context.Background(), path) assert.Nil(err, "read must succeed") assert.Equal([]byte("hello world"), bs) } diff --git a/bindings/go/types.go b/bindings/go/types.go index bd39c4177cee..19e300118314 100644 --- a/bindings/go/types.go +++ b/bindings/go/types.go @@ -126,7 +126,8 @@ var ( }[0], } - typeResultIsExist = ffi.Type{ + // typeResultExists mirrors opendal_result_exists { bool exists; opendal_error* error; } + typeResultExists = ffi.Type{ Type: ffi.Struct, Elements: &[]*ffi.Type{ &ffi.TypeUint8, @@ -276,6 +277,8 @@ type resultOperatorNew struct { type opendalOperator struct{} +type opendalCancelToken struct{} + type resultRead struct { data opendalBytes error *opendalError @@ -310,9 +313,10 @@ type resultReaderSeek struct { error *opendalError } -type resultIsExist struct { - is_exist uint8 - error *opendalError +// resultExists mirrors opendal_result_exists from the non-deprecated exists API. +type resultExists struct { + exists uint8 + error *opendalError } type resultStat struct { diff --git a/bindings/go/writer.go b/bindings/go/writer.go index 9c4415fe0829..ceabbf07d3a0 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -25,11 +25,14 @@ import ( "io" "runtime" "strings" + "sync" "unsafe" "github.com/jupiterrider/ffi" ) +var errWriterClosed = errors.New("writer is closed") + // Write writes the given bytes to the specified path. // // Write is a wrapper around the C-binding function `opendal_operator_write`. @@ -37,6 +40,8 @@ import ( // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native write in a blocking manner. // - path: The destination path where the bytes will be written. // - data: The byte slice containing the data to be written. // - opts: Optional write options. @@ -48,27 +53,29 @@ import ( // # Example // // func exampleWrite(op *opendal.Operator) { -// err = op.Write("test", []byte("Hello opendal go binding!")) +// err = op.Write(context.Background(), "test", []byte("Hello opendal go binding!")) // if err != nil { // log.Fatal(err) // } // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Write(path string, data []byte, opts ...WithWriteFn) error { - if len(opts) == 0 { - return ffiOperatorWrite.symbol(op.ctx)(op.inner, path, data) - } +func (op *Operator) Write(ctx context.Context, path string, data []byte, opts ...WithWriteFn) error { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + if len(opts) == 0 { + return ffiOperatorWriteWithCancel.symbol(op.ctx)(op.inner, path, data, token) + } - o := parseWriteOptions(opts...) - cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o) - if err != nil { + o := parseWriteOptions(opts...) + cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o) + if err != nil { + return err + } + defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts) + err = ffiOperatorWriteWithOptionsCancel.symbol(op.ctx)(op.inner, path, data, cOpts, token) + runtime.KeepAlive(keepAlive) return err - } - defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts) - err = ffiOperatorWriteWith.symbol(op.ctx)(op.inner, path, data, cOpts) - runtime.KeepAlive(keepAlive) - return err + }) } // WithWriteFn is a functional option for write operations. @@ -255,6 +262,8 @@ func newOpendalWriteOptions(ctx context.Context, o *writeOptions) (*opendalWrite // // # Parameters // +// - ctx: The context for the operation. Canceling it cancels the underlying +// native call in a blocking manner. // - path: The path where the directory should be created. // // # Returns @@ -275,7 +284,7 @@ func newOpendalWriteOptions(ctx context.Context, o *writeOptions) (*opendalWrite // # Example // // func exampleCreateDir(op *opendal.Operator) { -// err = op.CreateDir("test/") +// err = op.CreateDir(context.Background(), "test/") // if err != nil { // log.Fatal(err) // } @@ -283,8 +292,10 @@ func newOpendalWriteOptions(ctx context.Context, o *writeOptions) (*opendalWrite // // Note: This example assumes proper error handling and import statements. // The trailing slash in "test/" is important to indicate it's a directory. -func (op *Operator) CreateDir(path string) error { - return ffiOperatorCreateDir.symbol(op.ctx)(op.inner, path) +func (op *Operator) CreateDir(ctx context.Context, path string) error { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorCreateDirWithCancel.symbol(op.ctx)(op.inner, path, token) + }) } // Writer returns a new Writer for the specified path. @@ -295,6 +306,8 @@ func (op *Operator) CreateDir(path string) error { // // # Parameters // +// - ctx: The context bound to the returned Writer. It governs cancellation for +// all subsequent Write and Close calls on that Writer. // - path: The destination path where data will be written. // - opts: Optional write options. // @@ -305,7 +318,7 @@ func (op *Operator) CreateDir(path string) error { // # Example // // func exampleWriter(op *opendal.Operator) { -// writer, err := op.Writer("test/") +// writer, err := op.Writer(context.Background(), "test/") // if err != nil { // log.Fatal(err) // } @@ -317,40 +330,83 @@ func (op *Operator) CreateDir(path string) error { // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Writer(path string, opts ...WithWriteFn) (*Writer, error) { - if len(opts) == 0 { - inner, err := ffiOperatorWriter.symbol(op.ctx)(op.inner, path) +// The provided context is bound to the Writer; canceling it cancels in-flight +// Write and Close calls in a blocking manner. +func (op *Operator) Writer(ctx context.Context, path string, opts ...WithWriteFn) (*Writer, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (*Writer, error) { + if len(opts) == 0 { + inner, err := ffiOperatorWriterWithCancel.symbol(op.ctx)(op.inner, path, token) + if err != nil { + return nil, err + } + writer := &Writer{ + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, + } + return writer, nil + } + + o := parseWriteOptions(opts...) + cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o) + if err != nil { + return nil, err + } + defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts) + inner, err := ffiOperatorWriterWithOptionsCancel.symbol(op.ctx)(op.inner, path, cOpts, token) + runtime.KeepAlive(keepAlive) if err != nil { return nil, err } writer := &Writer{ - inner: inner, - ctx: op.ctx, + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, } return writer, nil - } - - o := parseWriteOptions(opts...) - cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o) - if err != nil { - return nil, err - } - defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts) - inner, err := ffiOperatorWriterWith.symbol(op.ctx)(op.inner, path, cOpts) - runtime.KeepAlive(keepAlive) - if err != nil { - return nil, err - } - writer := &Writer{ - inner: inner, - ctx: op.ctx, - } - return writer, nil + }, func(writer *Writer) { + if writer != nil { + writer.free() + } + }) } +// Writer implements io.WriteCloser. +// +// Writer is not safe for concurrent use. Callers must not call Write, Close, or +// free concurrently. The mutex protects only the idempotent-close check; it +// does not protect the native handle from concurrent FFI calls. +// +// After a cancelled Write or Close the handle remains valid and the same +// operation may be retried, but the writer's internal stream state is +// unspecified. Callers that need reliable delivery should discard the Writer +// and open a new one when a cancellation occurs. type Writer struct { inner *opendalWriter ctx context.Context + // cancelCtx is the user-provided context bound at creation. It governs + // cancellation for Write and Close so the Writer keeps stdlib io interface + // signatures. + cancelCtx context.Context + mu sync.Mutex +} + +func (w *Writer) cancelContext() context.Context { + if w.cancelCtx == nil { + return context.Background() + } + return w.cancelCtx +} + +func (w *Writer) free() { + w.mu.Lock() + defer w.mu.Unlock() + if w.inner == nil { + return + } + inner := w.inner + w.inner = nil + ffiWriterFree.symbol(w.ctx)(inner) } // Write writes the given bytes to the specified path. @@ -363,17 +419,19 @@ type Writer struct { // // # Parameters // -// - path: The destination path where the bytes will be written. -// - data: The byte slice containing the data to be written. +// - p: The byte slice containing the data to be written. // // # Returns // // - error: An error if the write operation fails, or nil if successful. // +// Write uses the context bound to the Writer at creation time. Canceling that +// context cancels the in-flight write in a blocking manner. +// // # Example // -// func exampleWrite(op *opendal.Operator) { -// err = op.Write("test", []byte("Hello opendal go binding!")) +// func exampleWrite(w *opendal.Writer) { +// _, err := w.Write([]byte("Hello opendal go binding!")) // if err != nil { // log.Fatal(err) // } @@ -381,25 +439,80 @@ type Writer struct { // // Note: This example assumes proper error handling and import statements. func (w *Writer) Write(p []byte) (n int, err error) { - return ffiWriterWrite.symbol(w.ctx)(w.inner, p) + ctx := w.cancelContext() + if err := ctx.Err(); err != nil { + return 0, err + } + + w.mu.Lock() + inner := w.inner + w.mu.Unlock() + if inner == nil { + return 0, errWriterClosed + } + + return runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (int, error) { + return ffiWriterWriteWithCancel.symbol(w.ctx)(inner, p, token) + }) } // Close finishes the write and releases the resources associated with the Writer. // It is important to call Close after writing all the data to ensure that the data is // properly flushed and written to the storage. Otherwise, the data may be lost. +// +// Close uses the context bound to the Writer at creation time. Canceling that +// context cancels the in-flight close in a blocking manner; in that case the +// underlying handle is preserved so Close can be retried. func (w *Writer) Close() error { - defer ffiWriterFree.symbol(w.ctx)(w.inner) - return ffiWriterClose.symbol(w.ctx)(w.inner) + ctx := w.cancelContext() + if err := ctx.Err(); err != nil { + return err + } + + w.mu.Lock() + if w.inner == nil { + w.mu.Unlock() + return nil + } + inner := w.inner + w.inner = nil + w.mu.Unlock() + + _, err := runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (struct{}, error) { + return struct{}{}, ffiWriterCloseWithCancel.symbol(w.ctx)(inner, token) + }) + if shouldReleaseWriterAfterClose(err) { + // On success or a native error the close attempt is final, so free the + // underlying handle. + ffiWriterFree.symbol(w.ctx)(inner) + return err + } + + // The close was canceled (context.Canceled/DeadlineExceeded). The native + // writer was not freed, so restore the handle to allow Close to be retried + // instead of leaking it. + // + // Note: re-closing after a cancelled close is best-effort. opendal does + // not document core::Writer::close() as resumable, so the retry may or may + // not succeed depending on the backend and how far the first attempt got. + w.mu.Lock() + w.inner = inner + w.mu.Unlock() + return err +} + +func shouldReleaseWriterAfterClose(err error) bool { + return !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) } var _ io.WriteCloser = (*Writer)(nil) -var ffiOperatorWrite = newFFI(ffiOpts{ - sym: "opendal_operator_write", +var ffiOperatorWriteWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_write_with_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, data []byte) error { - return func(op *opendalOperator, path string, data []byte) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, data []byte, token *opendalCancelToken) error { + return func(op *opendalOperator, path string, data []byte, token *opendalCancelToken) error { bytePath, err := BytePtrFromString(path) if err != nil { return err @@ -411,17 +524,18 @@ var ffiOperatorWrite = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&bytes), + unsafe.Pointer(&token), ) return parseError(ctx, e) } }) -var ffiOperatorWriteWith = newFFI(ffiOpts{ - sym: "opendal_operator_write_with", +var ffiOperatorWriteWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_write_with_options_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, data []byte, opts *opendalWriteOptions) error { - return func(op *opendalOperator, path string, data []byte, opts *opendalWriteOptions) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, data []byte, opts *opendalWriteOptions, token *opendalCancelToken) error { + return func(op *opendalOperator, path string, data []byte, opts *opendalWriteOptions, token *opendalCancelToken) error { bytePath, err := BytePtrFromString(path) if err != nil { return err @@ -434,17 +548,18 @@ var ffiOperatorWriteWith = newFFI(ffiOpts{ unsafe.Pointer(&bytePath), unsafe.Pointer(&bytes), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) return parseError(ctx, e) } }) -var ffiOperatorCreateDir = newFFI(ffiOpts{ - sym: "opendal_operator_create_dir", +var ffiOperatorCreateDirWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_create_dir_with_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) error { - return func(op *opendalOperator, path string) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) error { + return func(op *opendalOperator, path string, token *opendalCancelToken) error { bytePath, err := BytePtrFromString(path) if err != nil { return err @@ -454,17 +569,18 @@ var ffiOperatorCreateDir = newFFI(ffiOpts{ unsafe.Pointer(&e), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) return parseError(ctx, e) } }) -var ffiOperatorWriter = newFFI(ffiOpts{ - sym: "opendal_operator_writer", +var ffiOperatorWriterWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_writer_with_cancel", rType: &typeResultOperatorWriter, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string) (*opendalWriter, error) { - return func(op *opendalOperator, path string) (*opendalWriter, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalWriter, error) { + return func(op *opendalOperator, path string, token *opendalCancelToken) (*opendalWriter, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -474,6 +590,7 @@ var ffiOperatorWriter = newFFI(ffiOpts{ unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) if result.error != nil { return nil, parseError(ctx, result.error) @@ -482,12 +599,12 @@ var ffiOperatorWriter = newFFI(ffiOpts{ } }) -var ffiOperatorWriterWith = newFFI(ffiOpts{ - sym: "opendal_operator_writer_with", +var ffiOperatorWriterWithOptionsCancel = newFFI(ffiOpts{ + sym: "opendal_operator_writer_with_options_cancel", rType: &typeResultOperatorWriter, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalWriteOptions) (*opendalWriter, error) { - return func(op *opendalOperator, path string, opts *opendalWriteOptions) (*opendalWriter, error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path string, opts *opendalWriteOptions, token *opendalCancelToken) (*opendalWriter, error) { + return func(op *opendalOperator, path string, opts *opendalWriteOptions, token *opendalCancelToken) (*opendalWriter, error) { bytePath, err := BytePtrFromString(path) if err != nil { return nil, err @@ -498,6 +615,7 @@ var ffiOperatorWriterWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) if result.error != nil { return nil, parseError(ctx, result.error) @@ -638,18 +756,19 @@ var ffiWriterFree = newFFI(ffiOpts{ } }) -var ffiWriterWrite = newFFI(ffiOpts{ - sym: "opendal_writer_write", +var ffiWriterWriteWithCancel = newFFI(ffiOpts{ + sym: "opendal_writer_write_with_cancel", rType: &typeResultWriterWrite, - aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter, buf []byte) (size int, err error) { - return func(r *opendalWriter, buf []byte) (size int, err error) { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter, buf []byte, token *opendalCancelToken) (size int, err error) { + return func(r *opendalWriter, buf []byte, token *opendalCancelToken) (size int, err error) { bytes := toOpendalBytes(buf) var result resultWriterWrite ffiCall( unsafe.Pointer(&result), unsafe.Pointer(&r), unsafe.Pointer(&bytes), + unsafe.Pointer(&token), ) if result.error != nil { return 0, parseError(ctx, result.error) @@ -658,16 +777,17 @@ var ffiWriterWrite = newFFI(ffiOpts{ } }) -var ffiWriterClose = newFFI(ffiOpts{ - sym: "opendal_writer_close", +var ffiWriterCloseWithCancel = newFFI(ffiOpts{ + sym: "opendal_writer_close_with_cancel", rType: &ffi.TypePointer, - aTypes: []*ffi.Type{&ffi.TypePointer}, -}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter) error { - return func(r *opendalWriter) error { + aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer}, +}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter, token *opendalCancelToken) error { + return func(r *opendalWriter, token *opendalCancelToken) error { var e *opendalError ffiCall( unsafe.Pointer(&e), unsafe.Pointer(&r), + unsafe.Pointer(&token), ) return parseError(ctx, e) }