From 3441825362546acebac345385166853d883a2071 Mon Sep 17 00:00:00 2001 From: dentiny Date: Mon, 8 Jun 2026 11:57:09 +0000 Subject: [PATCH 01/13] Add context to go binding --- bindings/c/Cargo.toml | 3 +- bindings/c/README.md | 4 +- bindings/c/examples/basic.c | 4 +- bindings/c/examples/error_handle.c | 2 +- bindings/c/include/opendal.h | 331 ++++----- bindings/c/src/cancel.rs | 160 +++++ bindings/c/src/lib.rs | 3 + bindings/c/src/lister.rs | 62 +- bindings/c/src/operator.rs | 654 ++++++++---------- bindings/c/src/presign.rs | 168 +++-- bindings/c/src/reader.rs | 140 ++-- bindings/c/src/writer.rs | 68 +- bindings/c/tests/bdd.cpp | 32 +- bindings/c/tests/error_msg.cpp | 2 +- bindings/c/tests/example_test.cpp | 8 +- bindings/c/tests/list.cpp | 12 +- bindings/c/tests/reader.cpp | 16 +- bindings/c/tests/test_runner.cpp | 2 +- bindings/c/tests/test_suites_basic.cpp | 40 +- bindings/c/tests/test_suites_list.cpp | 114 +-- bindings/c/tests/test_suites_presign.cpp | 28 +- .../c/tests/test_suites_reader_writer.cpp | 62 +- bindings/d/source/opendal/operator.d | 20 +- bindings/go/context_test.go | 128 ++++ bindings/go/delete.go | 56 +- bindings/go/ffi.go | 103 +++ bindings/go/lister.go | 133 ++-- bindings/go/operator.go | 38 +- bindings/go/presign.go | 180 ++--- bindings/go/reader.go | 155 +++-- bindings/go/stat.go | 79 ++- bindings/go/string_ownership_test.go | 52 +- bindings/go/types.go | 2 + bindings/go/writer.go | 188 +++-- .../OpenDAL/Sources/OpenDAL/Operator.swift | 4 +- bindings/zig/src/opendal.zig | 20 +- bindings/zig/test/bdd.zig | 8 +- 37 files changed, 1763 insertions(+), 1318 deletions(-) create mode 100644 bindings/c/src/cancel.rs create mode 100644 bindings/go/context_test.go 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/README.md b/bindings/c/README.md index c4ad228fa0bd..a3697fb68372 100644 --- a/bindings/c/README.md +++ b/bindings/c/README.md @@ -31,11 +31,11 @@ int main() }; /* Write this into path "/testpath" */ - opendal_error *error = opendal_operator_write(op, "/testpath", &data); + opendal_error *error = opendal_operator_write_with_cancel(op, "/testpath", &data, NULL); assert(error == NULL); /* We can read it out, make sure the data is the same */ - opendal_result_read r = opendal_operator_read(op, "/testpath"); + opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); opendal_bytes read_bytes = r.data; assert(r.error == NULL); assert(read_bytes.len == 24); diff --git a/bindings/c/examples/basic.c b/bindings/c/examples/basic.c index 199403ca53b0..ee5bb4da14eb 100644 --- a/bindings/c/examples/basic.c +++ b/bindings/c/examples/basic.c @@ -37,11 +37,11 @@ int main() }; /* Write this into path "/testpath" */ - opendal_error* error = opendal_operator_write(op, "/testpath", &data); + opendal_error* error = opendal_operator_write_with_cancel(op, "/testpath", &data, NULL); assert(error == NULL); /* We can read it out, make sure the data is the same */ - opendal_result_read r = opendal_operator_read(op, "/testpath"); + opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); opendal_bytes read_bytes = r.data; assert(r.error == NULL); assert(read_bytes.len == 24); diff --git a/bindings/c/examples/error_handle.c b/bindings/c/examples/error_handle.c index 193a788afa97..1fc73ff7f329 100644 --- a/bindings/c/examples/error_handle.c +++ b/bindings/c/examples/error_handle.c @@ -32,7 +32,7 @@ int main() opendal_operator* op = result.op; /* The read is supposed to fail */ - opendal_result_read r = opendal_operator_read(op, "/testpath"); + opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); assert(r.error != NULL); assert(r.error->code == OPENDAL_NOT_FOUND); diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index d08668d141b3..337ecd9c50dd 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; @@ -1063,15 +1074,25 @@ extern "C" { void opendal_error_free(struct opendal_error *ptr); /** - * \brief Return the next object to be listed - * - * Lister is an iterator of the objects under its path, this method is the same as - * calling next() on the iterator - * - * For examples, please see the comment section of opendal_operator_list() - * @see opendal_operator_list() + * \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 Return the next object with cancellation support. */ -struct opendal_result_lister_next opendal_lister_next(struct opendal_lister *self); +struct opendal_result_lister_next opendal_lister_next_with_cancel(struct opendal_lister *self, + const struct opendal_cancel_token *token); /** * \brief Free the heap-allocated metadata used by opendal_lister @@ -1348,18 +1369,21 @@ struct opendal_result_operator_new opendal_operator_new_with_layers(const char * * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Write raw bytes to `path` with cancellation support. */ -struct opendal_error *opendal_operator_write(const struct opendal_operator *op, - const char *path, - const struct opendal_bytes *bytes); +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 Blocking write raw bytes to `path` with options. + * \brief Write raw bytes to `path` with options and cancellation support. */ -struct opendal_error *opendal_operator_write_with(const struct opendal_operator *op, - const char *path, - const struct opendal_bytes *bytes, - const struct opendal_write_options *opts); +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 Blocking read the data from `path`. @@ -1401,44 +1425,19 @@ struct opendal_error *opendal_operator_write_with(const struct opendal_operator * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Read data from `path` with cancellation support. */ -struct opendal_result_read opendal_operator_read(const struct opendal_operator *op, - const char *path); +struct opendal_result_read opendal_operator_read_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** - * \brief Blocking read the data from `path` with options. - * - * Read the data out from `path` blocking by operator, using the provided - * `opendal_read_options` to control the behavior, e.g. range, version, or - * conditional headers. - * - * @param op The opendal_operator created previously - * @param path The path you want to read the data out - * @param opts The options for the read operation; pass NULL to use defaults - * @see opendal_operator - * @see opendal_result_read - * @see opendal_read_options - * @see opendal_error - * @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated - * opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then - * the operation failed and the `data` field is a nullptr. - * - * \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. - * After your usage of that, please call opendal_bytes_free() to free the space. - * - * # Safety - * - * It is **safe** under the cases below - * * The memory pointed to by `path` must contain a valid nul terminator at the end of - * the string. - * - * # Panic - * - * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Read data from `path` with options and cancellation support. */ -struct opendal_result_read opendal_operator_read_with(const struct opendal_operator *op, - const char *path, - const struct opendal_read_options *opts); +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 Blocking read the data from `path`. @@ -1477,9 +1476,11 @@ struct opendal_result_read opendal_operator_read_with(const struct opendal_opera * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Create a reader with cancellation support. */ -struct opendal_result_operator_reader opendal_operator_reader(const struct opendal_operator *op, - const char *path); +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 Blocking create a writer for the specified path. @@ -1518,16 +1519,19 @@ struct opendal_result_operator_reader opendal_operator_reader(const struct opend * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Create a writer with cancellation support. */ -struct opendal_result_operator_writer opendal_operator_writer(const struct opendal_operator *op, - const char *path); +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 Blocking create a writer for the specified path with options. + * \brief Create a writer with options and cancellation support. */ -struct opendal_result_operator_writer opendal_operator_writer_with(const struct opendal_operator *op, - const char *path, - const struct opendal_write_options *opts); +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 Blocking delete the object in `path`. @@ -1570,34 +1574,19 @@ struct opendal_result_operator_writer opendal_operator_writer_with(const struct * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Delete the object in `path` with cancellation support. */ -struct opendal_error *opendal_operator_delete(const struct opendal_operator *op, const char *path); +struct opendal_error *opendal_operator_delete_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** - * \brief Blocking delete the object in `path` with options. - * - * Delete the object in `path` blocking by `op`, using the provided `opendal_delete_options`. - * This is similar to `opendal_operator_delete` but allows specifying a version or - * requesting a recursive delete. - * - * @param op The opendal_operator created previously - * @param path The designated path you want to delete - * @param opts The options for the delete operation; pass NULL to use defaults - * @see opendal_delete_options - * @return NULL if succeeds, otherwise it contains the error code and error message. - * - * # Safety - * - * * The memory pointed to by `path` must contain a valid nul terminator at the end of - * the string. - * - * # Panic - * - * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Delete the object in `path` with options and cancellation support. */ -struct opendal_error *opendal_operator_delete_with(const struct opendal_operator *op, - const char *path, - const struct opendal_delete_options *opts); +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 Check whether the path exists. @@ -1637,9 +1626,11 @@ struct opendal_error *opendal_operator_delete_with(const struct opendal_operator * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Check whether the path exists with cancellation support. */ -struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_operator *op, - const char *path); +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 Check whether the path exists. @@ -1679,9 +1670,11 @@ struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_op * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Check whether the path exists with cancellation support. */ -struct opendal_result_exists opendal_operator_exists(const struct opendal_operator *op, - const char *path); +struct opendal_result_exists opendal_operator_exists_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** * \brief Stat the path, return its metadata. @@ -1720,37 +1713,19 @@ struct opendal_result_exists opendal_operator_exists(const struct opendal_operat * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Stat the path with cancellation support. */ -struct opendal_result_stat opendal_operator_stat(const struct opendal_operator *op, - const char *path); +struct opendal_result_stat opendal_operator_stat_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** - * \brief Blocking stat the object in `path` with options. - * - * Stat the object in `path` with the provided `opendal_stat_options`. This is - * similar to `opendal_operator_stat` but allows passing options such as - * `version`, `if_match`, `if_none_match`, or response header overrides. - * - * @param op The opendal_operator created previously - * @param path The path you want to stat - * @param opts The options for the stat operation; pass NULL to use defaults - * @see opendal_operator - * @see opendal_result_stat - * @see opendal_stat_options - * @return Returns opendal_result_stat, containing a metadata and an opendal_error. - * - * # Safety - * - * * The memory pointed to by `path` must contain a valid nul terminator at the end of - * the string. - * - * # Panic - * - * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Stat the path with options and cancellation support. */ -struct opendal_result_stat opendal_operator_stat_with(const struct opendal_operator *op, - const char *path, - const struct opendal_stat_options *opts); +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 Blocking list the objects in `path`. @@ -1801,36 +1776,19 @@ struct opendal_result_stat opendal_operator_stat_with(const struct opendal_opera * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief List the objects in `path` with cancellation support. */ -struct opendal_result_list opendal_operator_list(const struct opendal_operator *op, - const char *path); +struct opendal_result_list opendal_operator_list_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** - * \brief Blocking list the objects in `path` with options. - * - * List the objects in `path` with the provided `opendal_list_options`. This is - * similar to `opendal_operator_list` but allows passing options such as - * `recursive` to control the listing behavior. - * - * @param op The opendal_operator created previously - * @param path The designated path you want to list - * @param opts The options for the list operation; pass NULL to use defaults - * @see opendal_lister - * @see opendal_list_options - * @return Returns opendal_result_list, containing a lister and an opendal_error. - * - * # Safety - * - * * The memory pointed to by `path` must contain a valid null terminator at the end of - * the string. - * - * # Panic - * - * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief List the objects in `path` with options and cancellation support. */ -struct opendal_result_list opendal_operator_list_with(const struct opendal_operator *op, - const char *path, - const struct opendal_list_options *opts); +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 Blocking create the directory in `path`. @@ -1866,9 +1824,11 @@ struct opendal_result_list opendal_operator_list_with(const struct opendal_opera * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * \brief Create the directory in `path` with cancellation support. */ -struct opendal_error *opendal_operator_create_dir(const struct opendal_operator *op, - const char *path); +struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opendal_operator *op, + const char *path, + const struct opendal_cancel_token *token); /** * \brief Blocking rename the object in `path`. @@ -1912,10 +1872,12 @@ struct opendal_error *opendal_operator_create_dir(const struct opendal_operator * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information + * \brief Rename the object with cancellation support. */ -struct opendal_error *opendal_operator_rename(const struct opendal_operator *op, - const char *src, - const char *dest); +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 Blocking copy the object in `path`. @@ -1959,12 +1921,15 @@ struct opendal_error *opendal_operator_rename(const struct opendal_operator *op, * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information + * \brief Copy the object with cancellation support. */ -struct opendal_error *opendal_operator_copy(const struct opendal_operator *op, - const char *src, - const char *dest); +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); -struct opendal_error *opendal_operator_check(const struct opendal_operator *op); +struct opendal_error *opendal_operator_check_with_cancel(const struct opendal_operator *op, + const struct opendal_cancel_token *token); /** * \brief Get information of underlying accessor. @@ -2024,32 +1989,36 @@ 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 Presign a read operation. + * \brief Presign a read operation with cancellation support. */ -struct opendal_result_presign opendal_operator_presign_read(const struct opendal_operator *op, - const char *path, - uint64_t expire_secs); +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 Presign a write operation. + * \brief Presign a write operation with cancellation support. */ -struct opendal_result_presign opendal_operator_presign_write(const struct opendal_operator *op, - const char *path, - uint64_t expire_secs); +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 Presign a delete operation. + * \brief Presign a delete operation with cancellation support. */ -struct opendal_result_presign opendal_operator_presign_delete(const struct opendal_operator *op, - const char *path, - uint64_t expire_secs); +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 Presign a stat operation. + * \brief Presign a stat operation with cancellation support. */ -struct opendal_result_presign opendal_operator_presign_stat(const struct opendal_operator *op, - const char *path, - uint64_t expire_secs); +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); /** * Get the method of the presigned request. @@ -2458,18 +2427,20 @@ struct opendal_metadata *opendal_entry_metadata(const struct opendal_entry *self void opendal_entry_free(struct opendal_entry *ptr); /** - * \brief Read data from the reader. + * \brief Read data from the reader with cancellation support. */ -struct opendal_result_reader_read opendal_reader_read(struct opendal_reader *self, - uint8_t *buf, - uintptr_t len); +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 Seek to an offset, in bytes, in a stream. + * \brief Seek to an offset with cancellation support. */ -struct opendal_result_reader_seek opendal_reader_seek(struct opendal_reader *self, - int64_t offset, - int32_t whence); +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 Frees the heap memory used by the opendal_reader. @@ -2477,15 +2448,17 @@ struct opendal_result_reader_seek opendal_reader_seek(struct opendal_reader *sel void opendal_reader_free(struct opendal_reader *ptr); /** - * \brief Write data to the writer. + * \brief Write data to the writer with cancellation support. */ -struct opendal_result_writer_write opendal_writer_write(struct opendal_writer *self, - const struct opendal_bytes *bytes); +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 Close the writer and make sure all data have been stored. + * \brief Close the writer with cancellation support. */ -struct opendal_error *opendal_writer_close(struct opendal_writer *ptr); +struct opendal_error *opendal_writer_close_with_cancel(struct opendal_writer *ptr, + const struct opendal_cancel_token *token); /** * \brief Frees the heap memory used by the opendal_writer. diff --git a/bindings/c/src/cancel.rs b/bindings/c/src/cancel.rs new file mode 100644 index 000000000000..f3dc46c63629 --- /dev/null +++ b/bindings/c/src/cancel.rs @@ -0,0 +1,160 @@ +// 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; + +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! { + result = fut => result, + _ = token.cancelled() => Err(cancelled_error()), + } + } + None => fut.await, + } +} + +pub(crate) fn cancelled_error() -> core::Error { + core::Error::new(core::ErrorKind::Unexpected, "operation cancelled") +} diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index 21d5d821bf86..ca1799421d46 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -36,6 +36,9 @@ mod error; pub use error::opendal_code; pub use error::opendal_error; +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..a16e6641dd9b 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -16,9 +16,12 @@ // under the License. use ::opendal as core; +use futures_util::StreamExt; use std::ffi::c_void; +use std::future::Future; use super::*; +use crate::operator::RUNTIME; /// \brief BlockingLister is designed to list entries at given path in a blocking /// manner. @@ -35,45 +38,38 @@ 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 _, } } - /// \brief Return the next object to be listed - /// - /// Lister is an iterator of the objects under its path, this method is the same as - /// calling next() on the iterator - /// - /// For examples, please see the comment section of opendal_operator_list() - /// @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 { + fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result + where + F: Future>, + { + let token = unsafe { cancel::clone_token(token) }; + RUNTIME.block_on(cancel::run(token, fut)) + } + + 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(), - }; - } - - 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), @@ -81,12 +77,24 @@ impl opendal_lister { } } + /// \brief Return the next object with cancellation support. + #[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(Self::block_on_cancelable(token, async move { + lister.next().await.transpose() + })) + } + /// \brief Free the heap-allocated metadata used by opendal_lister #[no_mangle] 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 00ae40968e76..e3a1682131e6 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::ffi::c_void; +use std::future::Future; use std::os::raw::c_char; use std::sync::LazyLock; @@ -24,7 +25,7 @@ use ::opendal as core; use super::*; -static RUNTIME: LazyLock = LazyLock::new(|| { +pub(crate) static RUNTIME: LazyLock = LazyLock::new(|| { tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -38,22 +39,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 +78,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 +103,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 +118,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 +133,99 @@ fn new_operator_result(op: core::Result) -> opendal_re } } +fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result +where + T: Send + 'static, + F: Future> + Send + 'static, +{ + let token = unsafe { cancel::clone_token(token) }; + RUNTIME + .block_on(RUNTIME.spawn(cancel::run(token, fut))) + .map_err(|err| { + core::Error::new(core::ErrorKind::Unexpected, "cancellable task failed").set_source(err) + })? +} + +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` @@ -265,43 +346,42 @@ pub unsafe extern "C" fn opendal_operator_new_with_layers( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Write raw bytes to `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_write( +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 { - 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), - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let bytes = core::Buffer::from(bytes); + let op = op.deref().clone(); + result_error(block_on_cancelable(token, async move { + op.write(&path, bytes).await.map(|_| ()) + })) } -/// \brief Blocking write raw bytes to `path` with options. +/// \brief Write raw bytes to `path` with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_write_with( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); + 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 { - (&*opts).into() + unsafe { (&*opts).into() } }; - match op.deref().write_options(path, bytes, opts) { - Ok(_) => std::ptr::null_mut(), - Err(e) => opendal_error::new(e), - } + let op = op.deref().clone(); + result_error(block_on_cancelable(token, async move { + op.write_options(&path, bytes, opts).await.map(|_| ()) + })) } /// \brief Blocking read the data from `path`. @@ -343,81 +423,39 @@ pub unsafe extern "C" fn opendal_operator_write_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Read data from `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_read( +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 { - 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), - }, - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_read(block_on_cancelable( + token, + async move { op.read(&path).await }, + )) } -/// \brief Blocking read the data from `path` with options. -/// -/// Read the data out from `path` blocking by operator, using the provided -/// `opendal_read_options` to control the behavior, e.g. range, version, or -/// conditional headers. -/// -/// @param op The opendal_operator created previously -/// @param path The path you want to read the data out -/// @param opts The options for the read operation; pass NULL to use defaults -/// @see opendal_operator -/// @see opendal_result_read -/// @see opendal_read_options -/// @see opendal_error -/// @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated -/// opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then -/// the operation failed and the `data` field is a nullptr. -/// -/// \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. -/// After your usage of that, please call opendal_bytes_free() to free the space. -/// -/// # Safety -/// -/// It is **safe** under the cases below -/// * The memory pointed to by `path` must contain a valid nul terminator at the end of -/// the string. -/// -/// # Panic -/// -/// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Read data from `path` with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_read_with( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); + let path = unsafe { parse_cstr(path, "path") }.to_owned(); let opts = if opts.is_null() { core::options::ReadOptions::default() } else { - (&*opts).into() + unsafe { (&*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), - }, - } + let op = op.deref().clone(); + result_read(block_on_cancelable(token, async move { + op.read_options(&path, opts).await + })) } /// \brief Blocking read the data from `path`. @@ -456,28 +494,18 @@ pub unsafe extern "C" fn opendal_operator_read_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Create a reader with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_reader( +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 { - 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(..) { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match block_on_cancelable(token, async move { op.reader(&path).await }) { Ok(reader) => opendal_result_operator_reader { - reader: Box::into_raw(Box::new(opendal_reader::new(reader))), + reader: Box::into_raw(Box::new(opendal_reader::new_async(reader))), error: std::ptr::null_mut(), }, Err(e) => opendal_result_operator_reader { @@ -523,60 +551,51 @@ pub unsafe extern "C" fn opendal_operator_reader( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Create a writer with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer( +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 { - 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(), + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match block_on_cancelable(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 Blocking create a writer for the specified path with options. +/// \brief Create a writer with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer_with( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); + let path = unsafe { parse_cstr(path, "path") }.to_owned(); 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), - }; - } + unsafe { (&*opts).into() } }; - - opendal_result_operator_writer { - writer: Box::into_raw(Box::new(opendal_writer::new(writer))), - error: std::ptr::null_mut(), + let op = op.deref().clone(); + match block_on_cancelable(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), + }, } } @@ -620,74 +639,35 @@ pub unsafe extern "C" fn opendal_operator_writer_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Delete the object in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete( +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 { - 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), - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_error(block_on_cancelable( + token, + async move { op.delete(&path).await }, + )) } -/// \brief Blocking delete the object in `path` with options. -/// -/// Delete the object in `path` blocking by `op`, using the provided `opendal_delete_options`. -/// This is similar to `opendal_operator_delete` but allows specifying a version or -/// requesting a recursive delete. -/// -/// @param op The opendal_operator created previously -/// @param path The designated path you want to delete -/// @param opts The options for the delete operation; pass NULL to use defaults -/// @see opendal_delete_options -/// @return NULL if succeeds, otherwise it contains the error code and error message. -/// -/// # Safety -/// -/// * The memory pointed to by `path` must contain a valid nul terminator at the end of -/// the string. -/// -/// # Panic -/// -/// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Delete the object in `path` with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete_with( +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 { - 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), - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = unsafe { parse_delete_options(opts) }; + let op = op.deref().clone(); + result_error(block_on_cancelable(token, async move { + op.delete_options(&path, opts).await + })) } /// \brief Check whether the path exists. @@ -727,17 +707,17 @@ pub unsafe extern "C" fn opendal_operator_delete_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Check whether the path exists with cancellation support. #[no_mangle] #[cfg_attr(cbindgen, cbindgen::ignore)] -pub unsafe extern "C" fn opendal_operator_is_exist( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().exists(path) { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match block_on_cancelable(token, async move { op.exists(&path).await }) { Ok(e) => opendal_result_is_exist { is_exist: e, error: std::ptr::null_mut(), @@ -786,16 +766,16 @@ pub unsafe extern "C" fn opendal_operator_is_exist( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Check whether the path exists with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_exists( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().exists(path) { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match block_on_cancelable(token, async move { op.exists(&path).await }) { Ok(e) => opendal_result_exists { exists: e, error: std::ptr::null_mut(), @@ -843,74 +823,39 @@ pub unsafe extern "C" fn opendal_operator_exists( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Stat the path with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat( +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 { - 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), - }, - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_stat(block_on_cancelable( + token, + async move { op.stat(&path).await }, + )) } -/// \brief Blocking stat the object in `path` with options. -/// -/// Stat the object in `path` with the provided `opendal_stat_options`. This is -/// similar to `opendal_operator_stat` but allows passing options such as -/// `version`, `if_match`, `if_none_match`, or response header overrides. -/// -/// @param op The opendal_operator created previously -/// @param path The path you want to stat -/// @param opts The options for the stat operation; pass NULL to use defaults -/// @see opendal_operator -/// @see opendal_result_stat -/// @see opendal_stat_options -/// @return Returns opendal_result_stat, containing a metadata and an opendal_error. -/// -/// # Safety -/// -/// * The memory pointed to by `path` must contain a valid nul terminator at the end of -/// the string. -/// -/// # Panic -/// -/// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Stat the path with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat_with( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); + let path = unsafe { parse_cstr(path, "path") }.to_owned(); let opts = if opts.is_null() { core::options::StatOptions::default() } else { - (&*opts).into() + unsafe { (&*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), - }, - } + let op = op.deref().clone(); + result_stat(block_on_cancelable(token, async move { + op.stat_options(&path, opts).await + })) } /// \brief Blocking list the objects in `path`. @@ -961,18 +906,18 @@ pub unsafe extern "C" fn opendal_operator_stat_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief List the objects in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_list( +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 { - assert!(!path.is_null()); - let path = std::ffi::CStr::from_ptr(path) - .to_str() - .expect("malformed path"); - match op.deref().lister(path) { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + match block_on_cancelable(token, async move { op.lister(&path).await }) { Ok(lister) => opendal_result_list { - lister: Box::into_raw(Box::new(opendal_lister::new(lister))), + lister: Box::into_raw(Box::new(opendal_lister::new_async(lister))), error: std::ptr::null_mut(), }, Err(e) => opendal_result_list { @@ -982,63 +927,20 @@ pub unsafe extern "C" fn opendal_operator_list( } } -/// \brief Blocking list the objects in `path` with options. -/// -/// List the objects in `path` with the provided `opendal_list_options`. This is -/// similar to `opendal_operator_list` but allows passing options such as -/// `recursive` to control the listing behavior. -/// -/// @param op The opendal_operator created previously -/// @param path The designated path you want to list -/// @param opts The options for the list operation; pass NULL to use defaults -/// @see opendal_lister -/// @see opendal_list_options -/// @return Returns opendal_result_list, containing a lister and an opendal_error. -/// -/// # Safety -/// -/// * The memory pointed to by `path` must contain a valid null terminator at the end of -/// the string. -/// -/// # Panic -/// -/// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief List the objects in `path` with options and cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_list_with( +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 { - 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) { + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let opts = unsafe { parse_list_options(opts) }; + let op = op.deref().clone(); + match block_on_cancelable(token, async move { op.lister_options(&path, opts).await }) { Ok(lister) => opendal_result_list { - lister: Box::into_raw(Box::new(opendal_lister::new(lister))), + lister: Box::into_raw(Box::new(opendal_lister::new_async(lister))), error: std::ptr::null_mut(), }, Err(e) => opendal_result_list { @@ -1081,20 +983,18 @@ pub unsafe extern "C" fn opendal_operator_list_with( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// \brief Create the directory in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_create_dir( +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 { - 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() - } + let path = unsafe { parse_cstr(path, "path") }.to_owned(); + let op = op.deref().clone(); + result_error(block_on_cancelable(token, async move { + op.create_dir(&path).await + })) } /// \brief Blocking rename the object in `path`. @@ -1138,25 +1038,20 @@ pub unsafe extern "C" fn opendal_operator_create_dir( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information +/// \brief Rename the object with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_rename( +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 { - 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() - } + 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(block_on_cancelable(token, async move { + op.rename(&src, &dest).await + })) } /// \brief Blocking copy the object in `path`. @@ -1200,32 +1095,27 @@ pub unsafe extern "C" fn opendal_operator_rename( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information +/// \brief Copy the object with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_copy( +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 { - 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() - } + 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(block_on_cancelable(token, async move { + op.copy(&src, &dest).await.map(|_| ()) + })) } #[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() - } +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(block_on_cancelable(token, async move { op.check().await })) } diff --git a/bindings/c/src/presign.rs b/bindings/c/src/presign.rs index 705f3334a8b9..1be9b2238c39 100644 --- a/bindings/c/src/presign.rs +++ b/bindings/c/src/presign.rs @@ -16,12 +16,17 @@ // under the License. use std::ffi::{c_char, CStr, CString}; +use std::future::Future; 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; +use crate::operator::RUNTIME; /// \brief The key-value pair for the headers of the presigned request. #[repr(C)] @@ -81,6 +86,38 @@ impl opendal_presigned_request_inner { } } +fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result +where + T: Send + 'static, + F: Future> + Send + 'static, +{ + let token = unsafe { cancel::clone_token(token) }; + RUNTIME + .block_on(RUNTIME.spawn(cancel::run(token, fut))) + .map_err(|err| { + core::Error::new(core::ErrorKind::Unexpected, "cancellable task failed").set_source(err) + })? +} + +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,127 +134,84 @@ pub struct opendal_result_presign { pub error: *mut opendal_error, } -/// \brief Presign a read operation. +/// \brief Presign a read operation with cancellation support. #[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); - - 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), - }, - } + let op = op.deref().clone(); + result_presign(block_on_cancelable(token, async move { + op.presign_read(&path, duration).await + })) } -/// \brief Presign a write operation. +/// \brief Presign a write operation with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_presign_write( +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 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); - - 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), - }, - } + let op = op.deref().clone(); + result_presign(block_on_cancelable(token, async move { + op.presign_write(&path, duration).await + })) } -/// \brief Presign a delete operation. +/// \brief Presign a delete operation with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_presign_delete( +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 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); - 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), - }, - } + let op = op.deref().clone(); + result_presign(block_on_cancelable(token, async move { + op.presign_delete(&path, duration).await + })) } -/// \brief Presign a stat operation. +/// \brief Presign a stat operation with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_presign_stat( +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 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); - - 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), - }, - } + let op = op.deref().clone(); + result_presign(block_on_cancelable(token, async move { + op.presign_stat(&path, duration).await + })) } /// Get the method of the presigned request. diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index 1335de0edf41..73d4e3066554 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -16,10 +16,11 @@ // under the License. use std::ffi::c_void; -use std::io::{Read, Seek, SeekFrom}; +use std::future::Future; use ::opendal as core; +use crate::operator::RUNTIME; use crate::result::opendal_result_reader_seek; use super::*; @@ -39,78 +40,98 @@ pub struct opendal_reader { inner: *mut c_void, } +struct AsyncReader { + reader: core::Reader, + position: u64, +} + 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) fn new_async(reader: core::Reader) -> Self { Self { - inner: Box::into_raw(Box::new(reader)) as _, + inner: Box::into_raw(Box::new(AsyncReader { + reader, + position: 0, + })) 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) { + fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result + where + F: Future>, + { + let token = unsafe { cancel::clone_token(token) }; + RUNTIME.block_on(cancel::run(token, fut)) + } + + async fn read_async_inner(reader: &mut AsyncReader, buf: &mut [u8]) -> core::Result { + let len = buf.len() as u64; + let data = reader + .reader + .read(reader.position..reader.position + len) + .await?; + let size = data.len().min(buf.len()); + buf[..size].copy_from_slice(&data.to_vec()[..size]); + reader.position += size as u64; + Ok(size) + } + + 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 Read data from the reader with cancellation support. #[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(Self::block_on_cancelable(token, async move { + Self::read_async_inner(reader, buf).await + })) + } + + /// \brief Seek to an offset with cancellation support. + #[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 Self::block_on_cancelable( + token, + async move { seek_async_reader(reader, offset, whence) }, + ) { 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), }, } } @@ -120,11 +141,42 @@ impl opendal_reader { 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)); } } } } + +fn seek_async_reader(reader: &mut AsyncReader, offset: i64, whence: i32) -> core::Result { + let base = match whence { + _x @ OPENDAL_SEEK_SET => 0, + _x @ OPENDAL_SEEK_CUR => reader.position as i64, + _x @ OPENDAL_SEEK_END => { + let Some(meta) = reader.reader.metadata() else { + return Err(core::Error::new( + core::ErrorKind::Unsupported, + "seek from end requires reader metadata", + )); + }; + meta.content_length() as i64 + } + _ => { + return Err(core::Error::new( + core::ErrorKind::Unexpected, + "undefined whence", + )); + } + }; + let position = base.checked_add(offset).ok_or_else(|| { + core::Error::new(core::ErrorKind::Unexpected, "reader seek position overflow") + })?; + if position < 0 { + return Err(core::Error::new( + core::ErrorKind::Unexpected, + "reader seek position is negative", + )); + } + reader.position = position as u64; + Ok(reader.position) +} diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs index 2b0eafa05a8c..0fd507365c74 100644 --- a/bindings/c/src/writer.rs +++ b/bindings/c/src/writer.rs @@ -17,8 +17,10 @@ use ::opendal as core; use std::ffi::c_void; +use std::future::Future; use super::*; +use crate::operator::RUNTIME; /// \brief The result type returned by opendal's writer operation. /// \note The opendal_writer actually owns a pointer to @@ -31,52 +33,70 @@ 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 block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result + where + F: Future>, + { + let token = unsafe { cancel::clone_token(token) }; + RUNTIME.block_on(cancel::run(token, fut)) + } + + 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 Write data to the writer with cancellation support. + #[no_mangle] + 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(Self::block_on_cancelable(token, async move { + writer.write(bytes).await?; + Ok(size) + })) + } + + /// \brief Close the writer with cancellation support. #[no_mangle] - pub unsafe extern "C" fn opendal_writer_close(ptr: *mut opendal_writer) -> *mut opendal_error { + 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) = + Self::block_on_cancelable(token, async move { writer.close().await }) + { + return opendal_error::new(e); } } std::ptr::null_mut() @@ -88,7 +108,7 @@ impl opendal_writer { 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/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp index 946df0c86630..25cbb2f48e54 100644 --- a/bindings/c/tests/bdd.cpp +++ b/bindings/c/tests/bdd.cpp @@ -61,19 +61,19 @@ TEST_F(OpendalBddTest, FeatureTest) .data = (uint8_t*)this->content.c_str(), .len = this->content.length(), }; - opendal_error* error = opendal_operator_check(this->p); + opendal_error* error = opendal_operator_check_with_cancel(this->p, nullptr); EXPECT_EQ(error, nullptr); - error = opendal_operator_write(this->p, this->path.c_str(), &data); + error = opendal_operator_write_with_cancel(this->p, this->path.c_str(), &data, nullptr); EXPECT_EQ(error, nullptr); // The blocking file "test" should exist - opendal_result_exists e = opendal_operator_exists(this->p, this->path.c_str()); + opendal_result_exists e = opendal_operator_exists_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(e.error, nullptr); EXPECT_TRUE(e.exists); // The blocking file "test" entry mode must be file - opendal_result_stat s = opendal_operator_stat(this->p, this->path.c_str()); + opendal_result_stat s = opendal_operator_stat_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(s.error, nullptr); opendal_metadata* meta = s.meta; EXPECT_TRUE(opendal_metadata_is_file(meta)); @@ -86,7 +86,7 @@ TEST_F(OpendalBddTest, FeatureTest) opendal_metadata_free(meta); // The blocking file "test" must have content "Hello, World!" - struct opendal_result_read r = opendal_operator_read(this->p, this->path.c_str()); + struct opendal_result_read r = opendal_operator_read_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(r.error, nullptr); EXPECT_EQ(r.data.len, this->content.length()); for (int i = 0; i < r.data.len; i++) { @@ -94,27 +94,27 @@ TEST_F(OpendalBddTest, FeatureTest) } // The blocking file should be deleted - error = opendal_operator_delete(this->p, this->path.c_str()); + error = opendal_operator_delete_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(error, nullptr); - e = opendal_operator_exists(this->p, this->path.c_str()); + e = opendal_operator_exists_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(e.error, nullptr); EXPECT_FALSE(e.exists); - opendal_result_operator_writer writer = opendal_operator_writer(this->p, this->path.c_str()); + opendal_result_operator_writer writer = opendal_operator_writer_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(writer.error, nullptr); - opendal_result_writer_write w = opendal_writer_write(writer.writer, &data); + opendal_result_writer_write w = opendal_writer_write_with_cancel(writer.writer, &data, nullptr); EXPECT_EQ(w.error, nullptr); EXPECT_EQ(w.size, this->content.length()); - opendal_error* close_err = opendal_writer_close(writer.writer); + opendal_error* close_err = opendal_writer_close_with_cancel(writer.writer, nullptr); EXPECT_EQ(close_err, nullptr); opendal_writer_free(writer.writer); // The blocking file "test" must have content "Hello, World!" and read into buffer int length = this->content.length(); unsigned char buffer[this->content.length()]; - opendal_result_operator_reader reader = opendal_operator_reader(this->p, this->path.c_str()); + opendal_result_operator_reader reader = opendal_operator_reader_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(reader.error, nullptr); - auto rst = opendal_reader_read(reader.reader, buffer, length); + auto rst = opendal_reader_read_with_cancel(reader.reader, buffer, length, nullptr); EXPECT_EQ(rst.size, length); for (int i = 0; i < this->content.length(); i++) { EXPECT_EQ(this->content[i], buffer[i]); @@ -122,19 +122,19 @@ TEST_F(OpendalBddTest, FeatureTest) opendal_reader_free(reader.reader); // The deletion operation should be idempotent - error = opendal_operator_delete(this->p, this->path.c_str()); + error = opendal_operator_delete_with_cancel(this->p, this->path.c_str(), nullptr); EXPECT_EQ(error, nullptr); opendal_bytes_free(&r.data); // The directory "tmpdir/" should exist and should be a directory - error = opendal_operator_create_dir(this->p, "tmpdir/"); + error = opendal_operator_create_dir_with_cancel(this->p, "tmpdir/", nullptr); EXPECT_EQ(error, nullptr); - auto stat = opendal_operator_stat(this->p, "tmpdir/"); + auto stat = opendal_operator_stat_with_cancel(this->p, "tmpdir/", nullptr); EXPECT_EQ(stat.error, nullptr); EXPECT_TRUE(opendal_metadata_is_dir(stat.meta)); EXPECT_FALSE(opendal_metadata_is_file(stat.meta)); opendal_metadata_free(stat.meta); - error = opendal_operator_delete(this->p, "tmpdir/"); + error = opendal_operator_delete_with_cancel(this->p, "tmpdir/", nullptr); EXPECT_EQ(error, nullptr); } diff --git a/bindings/c/tests/error_msg.cpp b/bindings/c/tests/error_msg.cpp index 16bc57aaed96..4813dda28499 100644 --- a/bindings/c/tests/error_msg.cpp +++ b/bindings/c/tests/error_msg.cpp @@ -49,7 +49,7 @@ TEST_F(OpendalErrorTest, ErrorReadTest) { // Initialize a operator for "memory" backend, with no options // The read is supposed to fail - opendal_result_read r = opendal_operator_read(this->p, "/testpath"); + opendal_result_read r = opendal_operator_read_with_cancel(this->p, "/testpath", nullptr); ASSERT_NE(r.error, nullptr); ASSERT_EQ(r.error->code, OPENDAL_NOT_FOUND); diff --git a/bindings/c/tests/example_test.cpp b/bindings/c/tests/example_test.cpp index bb945319470f..b7c4108e2f89 100644 --- a/bindings/c/tests/example_test.cpp +++ b/bindings/c/tests/example_test.cpp @@ -39,7 +39,7 @@ void simple_test() printf("Testing with service: %s\n", config->scheme); // Test basic operator functionality - opendal_error* error = opendal_operator_check(config->operator_instance); + opendal_error* error = opendal_operator_check_with_cancel(config->operator_instance, nullptr); if (error) { printf("Operator check failed: %d\n", error->code); if (error->message.data) { @@ -69,7 +69,7 @@ void simple_test() .len = strlen(test_content), .capacity = strlen(test_content) }; - error = opendal_operator_write(config->operator_instance, test_path, &data); + error = opendal_operator_write_with_cancel(config->operator_instance, test_path, &data, nullptr); if (error) { printf("Write failed: %d\n", error->code); opendal_error_free(error); @@ -77,7 +77,7 @@ void simple_test() printf("Write successful!\n"); // Read test data back - opendal_result_read result = opendal_operator_read(config->operator_instance, test_path); + opendal_result_read result = opendal_operator_read_with_cancel(config->operator_instance, test_path, nullptr); if (result.error) { printf("Read failed: %d\n", result.error->code); opendal_error_free(result.error); @@ -96,7 +96,7 @@ void simple_test() } // Cleanup - opendal_operator_delete(config->operator_instance, test_path); + opendal_operator_delete_with_cancel(config->operator_instance, test_path, nullptr); } } else { printf("Write/read not supported by this service\n"); diff --git a/bindings/c/tests/list.cpp b/bindings/c/tests/list.cpp index 391bc1e460d9..efc196d09622 100644 --- a/bindings/c/tests/list.cpp +++ b/bindings/c/tests/list.cpp @@ -65,11 +65,11 @@ TEST_F(OpendalListTest, ListDirTest) }; // write must succeed - EXPECT_EQ(opendal_operator_write(this->p, path.c_str(), &data), + EXPECT_EQ(opendal_operator_write_with_cancel(this->p, path.c_str(), &data, nullptr), nullptr); // list must succeed since the write succeeded - opendal_result_list l = opendal_operator_list(this->p, (dname + "/").c_str()); + opendal_result_list l = opendal_operator_list_with_cancel(this->p, (dname + "/").c_str(), nullptr); EXPECT_EQ(l.error, nullptr); opendal_lister* lister = l.lister; @@ -77,14 +77,14 @@ TEST_F(OpendalListTest, ListDirTest) // start checking the lister's result bool found = false; - opendal_result_lister_next result = opendal_lister_next(lister); + opendal_result_lister_next result = opendal_lister_next_with_cancel(lister, nullptr); EXPECT_EQ(result.error, nullptr); opendal_entry* entry = result.entry; while (entry) { char* de_path = opendal_entry_path(entry); // stat must succeed - opendal_result_stat s = opendal_operator_stat(this->p, de_path); + opendal_result_stat s = opendal_operator_stat_with_cancel(this->p, de_path, nullptr); EXPECT_EQ(s.error, nullptr); if (!strcmp(de_path, path.c_str())) { @@ -99,7 +99,7 @@ TEST_F(OpendalListTest, ListDirTest) opendal_metadata_free(s.meta); opendal_entry_free(entry); - result = opendal_lister_next(lister); + result = opendal_lister_next_with_cancel(lister, nullptr); EXPECT_EQ(result.error, nullptr); entry = result.entry; } @@ -108,7 +108,7 @@ TEST_F(OpendalListTest, ListDirTest) EXPECT_TRUE(found); // delete - EXPECT_EQ(opendal_operator_delete(this->p, path.c_str()), + EXPECT_EQ(opendal_operator_delete_with_cancel(this->p, path.c_str(), nullptr), nullptr); opendal_lister_free(lister); diff --git a/bindings/c/tests/reader.cpp b/bindings/c/tests/reader.cpp index 2cbde7f9ba5a..b5276f03edb4 100644 --- a/bindings/c/tests/reader.cpp +++ b/bindings/c/tests/reader.cpp @@ -54,41 +54,41 @@ TEST_F(OpendalReaderTest, SeekTest) }; // Prepare the file to be read immediately - opendal_error* err = opendal_operator_write(this->p, "/testseek", &data); + opendal_error* err = opendal_operator_write_with_cancel(this->p, "/testseek", &data, nullptr); EXPECT_EQ(err, nullptr); - opendal_result_operator_reader r = opendal_operator_reader(this->p, "/testseek"); + opendal_result_operator_reader r = opendal_operator_reader_with_cancel(this->p, "/testseek", nullptr); EXPECT_EQ(r.error, nullptr); // Test seek set - opendal_result_reader_seek seek_result = opendal_reader_seek(r.reader, 6, OPENDAL_SEEK_SET); + opendal_result_reader_seek seek_result = opendal_reader_seek_with_cancel(r.reader, 6, OPENDAL_SEEK_SET, nullptr); EXPECT_EQ(seek_result.pos, 6); EXPECT_EQ(seek_result.error, nullptr); char buf1[64] = { 0 }; - opendal_result_reader_read read_result = opendal_reader_read(r.reader, (uint8_t*)buf1, 7); + opendal_result_reader_read read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf1, 7, nullptr); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 7); EXPECT_EQ(std::string(buf1), "Gabcdef"); // Test seek cur, now we step on '3' - seek_result = opendal_reader_seek(r.reader, 3, OPENDAL_SEEK_CUR); + seek_result = opendal_reader_seek_with_cancel(r.reader, 3, OPENDAL_SEEK_CUR, nullptr); EXPECT_EQ(seek_result.pos, 16); EXPECT_EQ(seek_result.error, nullptr); char buf2[64] = { 0 }; - read_result = opendal_reader_read(r.reader, (uint8_t*)buf2, 32 /* no more 32 bytes*/); + read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf2, 32 /* no more 32 bytes*/, nullptr); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 5); EXPECT_EQ(std::string(buf2), "34567"); // Test seek end, now we step on 'g' - seek_result = opendal_reader_seek(r.reader, -8, OPENDAL_SEEK_END); + seek_result = opendal_reader_seek_with_cancel(r.reader, -8, OPENDAL_SEEK_END, nullptr); EXPECT_EQ(seek_result.pos, 13); EXPECT_EQ(seek_result.error, nullptr); char buf3[64] = { 0 }; - read_result = opendal_reader_read(r.reader, (uint8_t*)buf3, 32 /* no more 32 bytes*/); + read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf3, 32 /* no more 32 bytes*/, nullptr); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 8); EXPECT_EQ(std::string(buf3), "g1234567"); diff --git a/bindings/c/tests/test_runner.cpp b/bindings/c/tests/test_runner.cpp index cf1bf2397f3c..9e6bf2779df9 100644 --- a/bindings/c/tests/test_runner.cpp +++ b/bindings/c/tests/test_runner.cpp @@ -173,7 +173,7 @@ int main(int argc, char* argv[]) // Check operator availability - only perform list-based check if list is supported if (cap.list) { - opendal_error* check_error = opendal_operator_check(config->operator_instance); + opendal_error* check_error = opendal_operator_check_with_cancel(config->operator_instance, nullptr); if (check_error) { printf("Operator check failed: error code %d\n", check_error->code); if (check_error->message.data) { diff --git a/bindings/c/tests/test_suites_basic.cpp b/bindings/c/tests/test_suites_basic.cpp index a64454b622d4..62a405c25a55 100644 --- a/bindings/c/tests/test_suites_basic.cpp +++ b/bindings/c/tests/test_suites_basic.cpp @@ -30,7 +30,7 @@ void test_check(opendal_test_context* ctx) if (cap.list) { // Only perform the standard check if list operations are supported - opendal_error* error = opendal_operator_check(ctx->config->operator_instance); + opendal_error* error = opendal_operator_check_with_cancel(ctx->config->operator_instance, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Check operation should succeed"); } else { // For KV adapters that don't support list, the operator creation itself is @@ -53,11 +53,11 @@ void test_write_read(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Read data back - opendal_result_read result = opendal_operator_read(ctx->config->operator_instance, path); + opendal_result_read result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(result.error, "Read operation should succeed"); OPENDAL_ASSERT_EQ(strlen(content), result.data.len, "Read data length should match written data"); @@ -68,7 +68,7 @@ void test_write_read(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&result.data); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Exists operation @@ -78,7 +78,7 @@ void test_exists(opendal_test_context* ctx) const char* content = "test"; // File should not exist initially - opendal_result_exists result = opendal_operator_exists(ctx->config->operator_instance, path); + opendal_result_exists result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(!result.exists, "File should not exist initially"); @@ -88,16 +88,16 @@ void test_exists(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // File should exist now - result = opendal_operator_exists(ctx->config->operator_instance, path); + result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(result.exists, "File should exist after write"); // Cleanup - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Stat operation @@ -112,11 +112,11 @@ void test_stat(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Stat file - opendal_result_stat result = opendal_operator_stat(ctx->config->operator_instance, path); + opendal_result_stat result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(result.error, "Stat operation should succeed"); OPENDAL_ASSERT_NOT_NULL(result.meta, "Metadata should not be null"); @@ -131,7 +131,7 @@ void test_stat(opendal_test_context* ctx) // Cleanup opendal_metadata_free(result.meta); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Delete operation @@ -146,27 +146,27 @@ void test_delete(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Verify file exists - opendal_result_exists exists_result = opendal_operator_exists(ctx->config->operator_instance, path); + opendal_result_exists exists_result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(exists_result.error, "Exists operation should succeed"); OPENDAL_ASSERT(exists_result.exists, "File should exist before deletion"); // Delete file - error = opendal_operator_delete(ctx->config->operator_instance, path); + error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Delete operation should succeed"); // Verify file no longer exists - exists_result = opendal_operator_exists(ctx->config->operator_instance, path); + exists_result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(exists_result.error, "Exists operation should succeed"); OPENDAL_ASSERT(!exists_result.exists, "File should not exist after deletion"); // Delete should be idempotent - error = opendal_operator_delete(ctx->config->operator_instance, path); + error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Delete operation should be idempotent"); } @@ -176,16 +176,16 @@ void test_create_dir(opendal_test_context* ctx) const char* dir_path = "test_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // Verify directory exists - opendal_result_exists result = opendal_operator_exists(ctx->config->operator_instance, dir_path); + opendal_result_exists result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(result.exists, "Directory should exist after creation"); // Stat directory - opendal_result_stat stat_result = opendal_operator_stat(ctx->config->operator_instance, dir_path); + opendal_result_stat stat_result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(stat_result.error, "Stat operation should succeed"); OPENDAL_ASSERT(opendal_metadata_is_dir(stat_result.meta), "Should be identified as directory"); @@ -194,7 +194,7 @@ void test_create_dir(opendal_test_context* ctx) // Cleanup opendal_metadata_free(stat_result.meta); - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Define the basic test suite diff --git a/bindings/c/tests/test_suites_list.cpp b/bindings/c/tests/test_suites_list.cpp index 33aebe7ab052..3f878a4b35fa 100644 --- a/bindings/c/tests/test_suites_list.cpp +++ b/bindings/c/tests/test_suites_list.cpp @@ -28,7 +28,7 @@ void test_list_basic(opendal_test_context* ctx) const char* dir_path = "test_list_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // Create some test files @@ -42,20 +42,20 @@ void test_list_basic(opendal_test_context* ctx) data.data = (uint8_t*)"test content"; data.len = 12; data.capacity = 12; - error = opendal_operator_write(ctx->config->operator_instance, - test_files[i], &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, + test_files[i], &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); } // List directory - opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); + opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); // Collect all entries std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); + opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -91,9 +91,9 @@ void test_list_basic(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); for (size_t i = 0; i < num_files; i++) { - opendal_operator_delete(ctx->config->operator_instance, test_files[i]); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, test_files[i], nullptr); } - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Test: List empty directory @@ -102,18 +102,18 @@ void test_list_empty_dir(opendal_test_context* ctx) const char* dir_path = "test_empty_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); + opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); // Collect entries std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); + opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -142,7 +142,7 @@ void test_list_empty_dir(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Test: List nested directories @@ -154,10 +154,10 @@ void test_list_nested(opendal_test_context* ctx) const char* file_in_sub = "test_nested/subdir/sub_file.txt"; // Create directories - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, base_dir); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, base_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create base dir should succeed"); - error = opendal_operator_create_dir(ctx->config->operator_instance, sub_dir); + error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create sub dir should succeed"); // Create files @@ -166,21 +166,21 @@ void test_list_nested(opendal_test_context* ctx) data.len = 12; data.capacity = 12; - error = opendal_operator_write(ctx->config->operator_instance, file_in_base, - &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_in_base, + &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write to base dir should succeed"); - error = opendal_operator_write(ctx->config->operator_instance, file_in_sub, - &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_in_sub, + &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write to sub dir should succeed"); // List base directory - opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, base_dir); + opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, base_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); + opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -220,10 +220,10 @@ void test_list_nested(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete(ctx->config->operator_instance, file_in_sub); - opendal_operator_delete(ctx->config->operator_instance, file_in_base); - opendal_operator_delete(ctx->config->operator_instance, sub_dir); - opendal_operator_delete(ctx->config->operator_instance, base_dir); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_in_sub, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_in_base, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, base_dir, nullptr); } // Test: Entry name vs path @@ -233,23 +233,23 @@ void test_entry_name_path(opendal_test_context* ctx) const char* file_path = "test_entry_names/test_file.txt"; // Create directory and file - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)"test"; data.len = 4; data.capacity = 4; - error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); + opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); bool found_file = false; while (true) { - opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); + opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -279,8 +279,8 @@ void test_entry_name_path(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete(ctx->config->operator_instance, file_path); - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Test: Entry metadata from lister @@ -292,24 +292,24 @@ void test_entry_metadata(opendal_test_context* ctx) const size_t content_len = strlen(content_str); // Create directory and file - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)content_str; data.len = content_len; data.capacity = content_len; - error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); + opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); bool found_file = false; bool found_dir = false; while (true) { - opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); + opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -344,8 +344,8 @@ void test_entry_metadata(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete(ctx->config->operator_instance, file_path); - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Test: list_with default options (null opts behaves like list) @@ -354,25 +354,25 @@ void test_list_with_default_options(opendal_test_context* ctx) const char* dir_path = "test_list_with_default/"; const char* file_path = "test_list_with_default/file.txt"; - opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); + opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)"content"; data.len = 7; data.capacity = 7; - error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // Pass NULL opts — should behave identically to opendal_operator_list - opendal_result_list list_result = opendal_operator_list_with( - ctx->config->operator_instance, dir_path, NULL); + opendal_result_list list_result = opendal_operator_list_with_options_cancel( + ctx->config->operator_instance, dir_path, NULL, nullptr); OPENDAL_ASSERT_NO_ERROR(list_result.error, "list_with(NULL opts) should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); bool found_file = false; while (true) { - opendal_result_lister_next next = opendal_lister_next(list_result.lister); + opendal_result_lister_next next = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next.error) { OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail"); break; @@ -390,8 +390,8 @@ void test_list_with_default_options(opendal_test_context* ctx) OPENDAL_ASSERT(found_file, "Should find the file with null opts"); opendal_lister_free(list_result.lister); - opendal_operator_delete(ctx->config->operator_instance, file_path); - opendal_operator_delete(ctx->config->operator_instance, dir_path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); } // Test: list_with recursive=true returns entries in nested directories @@ -410,17 +410,17 @@ void test_list_with_recursive(opendal_test_context* ctx) data.capacity = 1; opendal_error* error; - error = opendal_operator_create_dir(ctx->config->operator_instance, base_dir); + error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, base_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create base dir should succeed"); - error = opendal_operator_create_dir(ctx->config->operator_instance, sub_dir); + error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create sub dir should succeed"); - error = opendal_operator_create_dir(ctx->config->operator_instance, deep_dir); + error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, deep_dir, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Create deep dir should succeed"); - error = opendal_operator_write(ctx->config->operator_instance, file_top, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_top, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write top file should succeed"); - error = opendal_operator_write(ctx->config->operator_instance, file_sub, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_sub, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write sub file should succeed"); - error = opendal_operator_write(ctx->config->operator_instance, file_deep, &data); + error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_deep, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write deep file should succeed"); // List recursively from base_dir @@ -428,8 +428,8 @@ void test_list_with_recursive(opendal_test_context* ctx) OPENDAL_ASSERT_NOT_NULL(opts, "list_options_new should not return NULL"); opendal_list_options_set_recursive(opts, true); - opendal_result_list list_result = opendal_operator_list_with( - ctx->config->operator_instance, base_dir, opts); + opendal_result_list list_result = opendal_operator_list_with_options_cancel( + ctx->config->operator_instance, base_dir, opts, nullptr); opendal_list_options_free(opts); OPENDAL_ASSERT_NO_ERROR(list_result.error, "Recursive list should succeed"); @@ -437,7 +437,7 @@ void test_list_with_recursive(opendal_test_context* ctx) std::unordered_set found_paths; while (true) { - opendal_result_lister_next next = opendal_lister_next(list_result.lister); + opendal_result_lister_next next = opendal_lister_next_with_cancel(list_result.lister, nullptr); if (next.error) { OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail"); break; @@ -460,12 +460,12 @@ void test_list_with_recursive(opendal_test_context* ctx) "Recursive list must include file in deep directory"); // Cleanup - opendal_operator_delete(ctx->config->operator_instance, file_deep); - opendal_operator_delete(ctx->config->operator_instance, file_sub); - opendal_operator_delete(ctx->config->operator_instance, file_top); - opendal_operator_delete(ctx->config->operator_instance, deep_dir); - opendal_operator_delete(ctx->config->operator_instance, sub_dir); - opendal_operator_delete(ctx->config->operator_instance, base_dir); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_deep, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_sub, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_top, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, deep_dir, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, base_dir, nullptr); } // Define the list test suite diff --git a/bindings/c/tests/test_suites_presign.cpp b/bindings/c/tests/test_suites_presign.cpp index 4ade2815f877..616ee55a7932 100644 --- a/bindings/c/tests/test_suites_presign.cpp +++ b/bindings/c/tests/test_suites_presign.cpp @@ -391,10 +391,10 @@ void test_presign_read(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_read(ctx->config->operator_instance, path, 3600); + opendal_result_presign presign_result = opendal_operator_presign_read_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign read should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -464,7 +464,7 @@ void test_presign_read(opendal_test_context* ctx) opendal_presigned_request_free(presign_result.req); - opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); + opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); OPENDAL_ASSERT(header_found, "Content-Length header should be present"); @@ -482,7 +482,7 @@ void test_presign_write(opendal_test_context* ctx) const char* content = "Presign write content"; size_t content_len = strlen(content); - opendal_result_presign presign_result = opendal_operator_presign_write(ctx->config->operator_instance, path, 3600); + opendal_result_presign presign_result = opendal_operator_presign_write_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign write should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -556,7 +556,7 @@ void test_presign_write(opendal_test_context* ctx) presign_cleanup_curl(curl, chunk); opendal_presigned_request_free(presign_result.req); - opendal_result_stat stat_res = opendal_operator_stat(ctx->config->operator_instance, path); + opendal_result_stat stat_res = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(stat_res.error, "Stat after presign write should succeed"); OPENDAL_ASSERT_NOT_NULL(stat_res.meta, "Stat metadata should not be null"); OPENDAL_ASSERT_EQ(content_len, (size_t)opendal_metadata_content_length(stat_res.meta), @@ -564,14 +564,14 @@ void test_presign_write(opendal_test_context* ctx) opendal_metadata_free(stat_res.meta); - opendal_result_read read_res = opendal_operator_read(ctx->config->operator_instance, path); + opendal_result_read read_res = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(read_res.error, "Read after presign write should succeed"); OPENDAL_ASSERT_EQ(content_len, read_res.data.len, "Read length should match uploaded content length"); OPENDAL_ASSERT(memcmp(content, read_res.data.data, read_res.data.len) == 0, "Read content should match uploaded content"); opendal_bytes_free(&read_res.data); - opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); + opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); } @@ -586,10 +586,10 @@ void test_presign_stat(opendal_test_context* ctx) data.data = (uint8_t*)content; data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_stat(ctx->config->operator_instance, path, 3600); + opendal_result_presign presign_result = opendal_operator_presign_stat_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign stat should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -652,7 +652,7 @@ void test_presign_stat(opendal_test_context* ctx) OPENDAL_ASSERT_EQ(content_len, header_ctx.content_length, "Stat Content-Length should match written data"); - opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); + opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); } @@ -674,10 +674,10 @@ void test_presign_delete(opendal_test_context* ctx) data.data = (uint8_t*)content; data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_delete(ctx->config->operator_instance, path, 3600); + opendal_result_presign presign_result = opendal_operator_presign_delete_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign delete should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -712,12 +712,12 @@ void test_presign_delete(opendal_test_context* ctx) presign_cleanup_curl(curl, chunk); opendal_presigned_request_free(presign_result.req); - opendal_result_exists exists_res = opendal_operator_exists(ctx->config->operator_instance, path); + opendal_result_exists exists_res = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(exists_res.error, "Exists after presign delete should succeed"); OPENDAL_ASSERT(!exists_res.exists, "Object should not exist after presign delete"); // Ensure cleanup is idempotent - opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); + opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(delete_error, "Delete after presign delete should be idempotent"); } diff --git a/bindings/c/tests/test_suites_reader_writer.cpp b/bindings/c/tests/test_suites_reader_writer.cpp index 0163f2024eab..29652e093466 100644 --- a/bindings/c/tests/test_suites_reader_writer.cpp +++ b/bindings/c/tests/test_suites_reader_writer.cpp @@ -31,18 +31,18 @@ void test_reader_basic(opendal_test_context* ctx) .data = (uint8_t*)content, .len = content_len, .capacity = content_len }; - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); + opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); OPENDAL_ASSERT_NOT_NULL(reader_result.reader, "Reader should not be null"); // Read entire content uint8_t buffer[100]; - opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, sizeof(buffer)); + opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, sizeof(buffer), nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read operation should succeed"); OPENDAL_ASSERT_EQ(content_len, read_result.size, "Read size should match content length"); @@ -53,7 +53,7 @@ void test_reader_basic(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Reader seek operations @@ -68,49 +68,49 @@ void test_reader_seek(opendal_test_context* ctx) .data = (uint8_t*)content, .len = content_len, .capacity = content_len }; - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); + opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); // Test seek from current position - opendal_result_reader_seek seek_result = opendal_reader_seek(reader_result.reader, 5, OPENDAL_SEEK_CUR); + opendal_result_reader_seek seek_result = opendal_reader_seek_with_cancel(reader_result.reader, 5, OPENDAL_SEEK_CUR, nullptr); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from current should succeed"); OPENDAL_ASSERT_EQ(5, seek_result.pos, "Position should be 5"); // Read after seek uint8_t buffer[5]; - opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, 5); + opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read after seek should succeed"); OPENDAL_ASSERT_EQ(5, read_result.size, "Should read 5 bytes"); OPENDAL_ASSERT(memcmp("56789", buffer, 5) == 0, "Should read correct content after seek"); // Test seek from beginning - seek_result = opendal_reader_seek(reader_result.reader, 0, OPENDAL_SEEK_SET); + seek_result = opendal_reader_seek_with_cancel(reader_result.reader, 0, OPENDAL_SEEK_SET, nullptr); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from beginning should succeed"); OPENDAL_ASSERT_EQ(0, seek_result.pos, "Position should be 0"); // Read from beginning - read_result = opendal_reader_read(reader_result.reader, buffer, 5); + read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read from beginning should succeed"); OPENDAL_ASSERT(memcmp("01234", buffer, 5) == 0, "Should read correct content from beginning"); // Test seek from end - seek_result = opendal_reader_seek(reader_result.reader, -5, OPENDAL_SEEK_END); + seek_result = opendal_reader_seek_with_cancel(reader_result.reader, -5, OPENDAL_SEEK_END, nullptr); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from end should succeed"); OPENDAL_ASSERT_EQ(content_len - 5, seek_result.pos, "Position should be content_len - 5"); // Read from near end - read_result = opendal_reader_read(reader_result.reader, buffer, 5); + read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read from near end should succeed"); OPENDAL_ASSERT(memcmp("FGHIJ", buffer, 5) == 0, @@ -118,7 +118,7 @@ void test_reader_seek(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Basic writer operations @@ -129,7 +129,7 @@ void test_writer_basic(opendal_test_context* ctx) const char* content2 = "OpenDAL Writer!"; // Create writer - opendal_result_operator_writer writer_result = opendal_operator_writer(ctx->config->operator_instance, path); + opendal_result_operator_writer writer_result = opendal_operator_writer_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(writer_result.error, "Writer creation should succeed"); OPENDAL_ASSERT_NOT_NULL(writer_result.writer, "Writer should not be null"); @@ -140,7 +140,7 @@ void test_writer_basic(opendal_test_context* ctx) data1.len = strlen(content1); data1.capacity = strlen(content1); - opendal_result_writer_write write_result = opendal_writer_write(writer_result.writer, &data1); + opendal_result_writer_write write_result = opendal_writer_write_with_cancel(writer_result.writer, &data1, nullptr); OPENDAL_ASSERT_NO_ERROR(write_result.error, "First write should succeed"); OPENDAL_ASSERT_EQ(strlen(content1), write_result.size, "Write size should match content length"); @@ -151,18 +151,18 @@ void test_writer_basic(opendal_test_context* ctx) data2.len = strlen(content2); data2.capacity = strlen(content2); - write_result = opendal_writer_write(writer_result.writer, &data2); + write_result = opendal_writer_write_with_cancel(writer_result.writer, &data2, nullptr); // Check if this is a OneShotWriter limitation if (write_result.error != NULL && write_result.error->message.data != NULL && strstr((char*)write_result.error->message.data, "OneShotWriter doesn't support multiple write") != NULL) { printf("Note: Service uses OneShotWriter, skipping multiple write test\n"); // Close current writer and verify single write worked - opendal_error* error = opendal_writer_close(writer_result.writer); + opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify first write content - opendal_result_read read_result = opendal_operator_read(ctx->config->operator_instance, path); + opendal_result_read read_result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); OPENDAL_ASSERT_EQ(strlen(content1), read_result.data.len, "Content length should match first write"); @@ -173,7 +173,7 @@ void test_writer_basic(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&read_result.data); opendal_writer_free(writer_result.writer); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); return; } @@ -182,11 +182,11 @@ void test_writer_basic(opendal_test_context* ctx) "Write size should match content length"); // Close writer - opendal_error* error = opendal_writer_close(writer_result.writer); + opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify written content - opendal_result_read read_result = opendal_operator_read(ctx->config->operator_instance, path); + opendal_result_read read_result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); size_t expected_len = strlen(content1) + strlen(content2); @@ -205,7 +205,7 @@ void test_writer_basic(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&read_result.data); opendal_writer_free(writer_result.writer); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Writer with large data @@ -216,7 +216,7 @@ void test_writer_large_data(opendal_test_context* ctx) const size_t num_chunks = 10; // Create writer - opendal_result_operator_writer writer_result = opendal_operator_writer(ctx->config->operator_instance, path); + opendal_result_operator_writer writer_result = opendal_operator_writer_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(writer_result.error, "Writer creation should succeed"); @@ -233,7 +233,7 @@ void test_writer_large_data(opendal_test_context* ctx) bool is_one_shot_writer = false; for (size_t i = 0; i < num_chunks; i++) { - opendal_result_writer_write write_result = opendal_writer_write(writer_result.writer, &chunk); + opendal_result_writer_write write_result = opendal_writer_write_with_cancel(writer_result.writer, &chunk, nullptr); // Check for OneShotWriter limitation on subsequent writes if (i > 0 && write_result.error != NULL && write_result.error->message.data != NULL && strstr((char*)write_result.error->message.data, "OneShotWriter doesn't support multiple write") != NULL) { @@ -249,11 +249,11 @@ void test_writer_large_data(opendal_test_context* ctx) } // Close writer - opendal_error* error = opendal_writer_close(writer_result.writer); + opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify total size - adjust expectations for OneShotWriter - opendal_result_stat stat_result = opendal_operator_stat(ctx->config->operator_instance, path); + opendal_result_stat stat_result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(stat_result.error, "Stat should succeed"); if (is_one_shot_writer) { @@ -272,7 +272,7 @@ void test_writer_large_data(opendal_test_context* ctx) free(chunk_data); opendal_metadata_free(stat_result.meta); opendal_writer_free(writer_result.writer); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Test: Reader partial read @@ -288,11 +288,11 @@ void test_reader_partial_read(opendal_test_context* ctx) data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); + opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); + opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); @@ -302,7 +302,7 @@ void test_reader_partial_read(opendal_test_context* ctx) size_t total_read = 0; while (total_read < content_len) { - opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, chunk_size); + opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, chunk_size, nullptr); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); if (read_result.size == 0) { @@ -321,7 +321,7 @@ void test_reader_partial_read(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete(ctx->config->operator_instance, path); + opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); } // Define the reader/writer test suite diff --git a/bindings/d/source/opendal/operator.d b/bindings/d/source/opendal/operator.d index a3fe616fbf0c..a6f3330343b1 100644 --- a/bindings/d/source/opendal/operator.d +++ b/bindings/d/source/opendal/operator.d @@ -48,7 +48,7 @@ struct Operator void write(string path, ubyte[] data) @trusted { opendal_bytes bytes = opendal_bytes(data.ptr, data.length, data.length); - auto error = opendal_operator_write(op, path.toStringz, &bytes); + auto error = opendal_operator_write_with_cancel(op, path.toStringz, &bytes, null); enforce(error is null, "Error writing data"); } @@ -75,7 +75,7 @@ struct Operator ubyte[] read(string path) @trusted { - auto result = opendal_operator_read(op, path.toStringz); + auto result = opendal_operator_read_with_cancel(op, path.toStringz, null); enforce(result.error is null, "Error reading data"); scope (exit) opendal_bytes_free(&result.data); @@ -84,33 +84,33 @@ struct Operator void remove(string path) @trusted { - auto error = opendal_operator_delete(op, path.toStringz); + auto error = opendal_operator_delete_with_cancel(op, path.toStringz, null); enforce(error is null, "Error deleting object"); } bool exists(string path) @trusted { - auto result = opendal_operator_exists(op, path.toStringz); + auto result = opendal_operator_exists_with_cancel(op, path.toStringz, null); enforce(result.error is null, "Error checking existence"); return result.exists; } Metadata stat(string path) @trusted { - auto result = opendal_operator_stat(op, path.toStringz); + auto result = opendal_operator_stat_with_cancel(op, path.toStringz, null); enforce(result.error is null, "Error getting metadata"); return Metadata(result.meta); } Entry[] list(string path) @trusted { - auto result = opendal_operator_list(op, path.toStringz); + auto result = opendal_operator_list_with_cancel(op, path.toStringz, null); enforce(result.error is null, "Error listing objects"); Entry[] entries; while (true) { - auto next = opendal_lister_next(result.lister); + auto next = opendal_lister_next_with_cancel(result.lister, null); if (next.entry is null) break; entries ~= Entry(next.entry); @@ -120,19 +120,19 @@ struct Operator void createDir(string path) @trusted { - auto error = opendal_operator_create_dir(op, path.toStringz); + auto error = opendal_operator_create_dir_with_cancel(op, path.toStringz, null); enforce(error is null, "Error creating directory"); } void rename(string src, string dest) @trusted { - auto error = opendal_operator_rename(op, src.toStringz, dest.toStringz); + auto error = opendal_operator_rename_with_cancel(op, src.toStringz, dest.toStringz, null); enforce(error is null, "Error renaming object"); } void copy(string src, string dest) @trusted { - auto error = opendal_operator_copy(op, src.toStringz, dest.toStringz); + auto error = opendal_operator_copy_with_cancel(op, src.toStringz, dest.toStringz, null); enforce(error is null, "Error copying object"); } diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go new file mode 100644 index 000000000000..4915da1ae04f --- /dev/null +++ b/bindings/go/context_test.go @@ -0,0 +1,128 @@ +/* + * 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 TestOperatorWithContextMethodsReturnCanceledContext(t *testing.T) { + ctx := newCanceledContext() + op := &Operator{} + + if _, err := op.ReadWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("ReadWithContext() error = %v, want context.Canceled", err) + } + if err := op.WriteWithContext(ctx, "path", []byte("data")); !errors.Is(err, context.Canceled) { + t.Fatalf("WriteWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.StatWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("StatWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.IsExistWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("IsExistWithContext() error = %v, want context.Canceled", err) + } + if err := op.DeleteWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("DeleteWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.ListWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("ListWithContext() error = %v, want context.Canceled", err) + } + if err := op.CheckWithContext(ctx); !errors.Is(err, context.Canceled) { + t.Fatalf("CheckWithContext() error = %v, want context.Canceled", err) + } + if err := op.CreateDirWithContext(ctx, "path/"); !errors.Is(err, context.Canceled) { + t.Fatalf("CreateDirWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.ReaderWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("ReaderWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.WriterWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("WriterWithContext() error = %v, want context.Canceled", err) + } + if err := op.CopyWithContext(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { + t.Fatalf("CopyWithContext() error = %v, want context.Canceled", err) + } + if err := op.RenameWithContext(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { + t.Fatalf("RenameWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.PresignReadWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignReadWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.PresignWriteWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignWriteWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.PresignDeleteWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignDeleteWithContext() error = %v, want context.Canceled", err) + } + if _, err := op.PresignStatWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { + t.Fatalf("PresignStatWithContext() error = %v, want context.Canceled", err) + } +} + +func TestIOWithContextMethodsReturnCanceledContext(t *testing.T) { + ctx := newCanceledContext() + + reader := &Reader{} + if _, err := reader.ReadWithContext(ctx, make([]byte, 1)); !errors.Is(err, context.Canceled) { + t.Fatalf("Reader.ReadWithContext() error = %v, want context.Canceled", err) + } + if _, err := reader.SeekWithContext(ctx, 0, 0); !errors.Is(err, context.Canceled) { + t.Fatalf("Reader.SeekWithContext() error = %v, want context.Canceled", err) + } + + writer := &Writer{} + if _, err := writer.WriteWithContext(ctx, []byte("data")); !errors.Is(err, context.Canceled) { + t.Fatalf("Writer.WriteWithContext() error = %v, want context.Canceled", err) + } + + lister := &Lister{} + if lister.NextWithContext(ctx) { + t.Fatal("Lister.NextWithContext() = true, want false") + } + if !errors.Is(lister.Error(), context.Canceled) { + t.Fatalf("Lister.Error() = %v, want context.Canceled", lister.Error()) + } +} + +func TestRunWithContextReturnsDeadlineExceeded(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + + done := make(chan struct{}) + defer close(done) + + _, err := runWithContext(ctx, func() (struct{}, error) { + <-done + return struct{}{}, nil + }) + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("runWithContext() error = %v, want context.DeadlineExceeded", err) + } +} diff --git a/bindings/go/delete.go b/bindings/go/delete.go index 137f6d0ef3dd..609f040080b3 100644 --- a/bindings/go/delete.go +++ b/bindings/go/delete.go @@ -90,28 +90,34 @@ type deleteOptions struct { // // 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) + return op.DeleteWithContext(context.Background(), path, opts...) } -var ffiOperatorDelete = newFFI(ffiOpts{ - sym: "opendal_operator_delete", +func (op *Operator) DeleteWithContext(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 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 +127,7 @@ var ffiOperatorDelete = newFFI(ffiOpts{ unsafe.Pointer(&e), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) return parseError(ctx, e) } @@ -186,12 +193,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 +209,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..da298c5eb637 100644 --- a/bindings/go/ffi.go +++ b/bindings/go/ffi.go @@ -36,6 +36,72 @@ type ffiOpts struct { type ffiCall func(rValue unsafe.Pointer, aValues ...unsafe.Pointer) +type contextResult[T any] struct { + value T + err error +} + +func runWithContext[T any](ctx context.Context, fn func() (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 + } + return fn() +} + +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(): + ffiCancelTokenCancel.symbol(ffiCtx)(token) + result := <-ch + for _, cleanup := range cleanup { + if cleanup != nil { + cleanup(result.value) + } + } + return zero, ctx.Err() + } +} + +func runErrWithContext(ctx context.Context, fn func() error) error { + _, err := runWithContext(ctx, func() (struct{}, error) { + return struct{}{}, fn() + }) + return 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 +191,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 8a3fd9bb2874..6de05ebc1308 100644 --- a/bindings/go/lister.go +++ b/bindings/go/lister.go @@ -54,24 +54,31 @@ import ( // // Note: This example assumes proper error handling and import statements. func (op *Operator) Check() (err error) { - ds, err := op.List("/") - if err != nil { - return - } - defer func() { - closeErr := ds.Close() - if err == nil { - err = closeErr - } - }() - ds.Next() - err = ds.Error() - if err, ok := err.(*Error); ok && err.Code() == CodeNotFound { - return nil - } - return + return op.CheckWithContext(context.Background()) +} + +func (op *Operator) CheckWithContext(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. @@ -175,29 +182,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 + return op.ListWithContext(context.Background(), path, opts...) +} + +func (op *Operator) ListWithContext(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, + }, nil + }, func(lister *Lister) { + if lister != nil { + _ = lister.Close() + } + }) } // Lister provides a mechanism for listing entries at a specified path. @@ -284,15 +301,23 @@ func (l *Lister) Error() error { // fmt.Println(entry.Name()) // } func (l *Lister) Next() bool { - inner, err := ffiListerNext.symbol(l.ctx)(l.inner) - if inner == nil || err != nil { + return l.NextWithContext(context.Background()) +} + +func (l *Lister) NextWithContext(ctx context.Context) bool { + entry, err := runWithCancelContext(ctx, 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 } @@ -482,12 +507,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 @@ -498,6 +523,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) @@ -519,16 +545,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/operator.go b/bindings/go/operator.go index 792b0d953abf..9b77ff36e85e 100644 --- a/bindings/go/operator.go +++ b/bindings/go/operator.go @@ -60,7 +60,13 @@ import ( // // Note: This example assumes proper error handling and import statements. func (op *Operator) Copy(src, dest string) error { - return ffiOperatorCopy.symbol(op.ctx)(op.inner, src, dest) + return op.CopyWithContext(context.Background(), src, dest) +} + +func (op *Operator) CopyWithContext(ctx context.Context, src, dest string) error { + return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { + return ffiOperatorCopyWithCancel.symbol(op.ctx)(op.inner, src, dest, token) + }) } // Rename changes the name or location of a file from the source path to the destination path. @@ -95,7 +101,13 @@ func (op *Operator) Copy(src, dest string) error { // // 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) + return op.RenameWithContext(context.Background(), src, dest) +} + +func (op *Operator) RenameWithContext(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) { @@ -195,12 +207,12 @@ var ffiOperatorOptionsFree = newFFI(ffiOpts{ } }) -var ffiOperatorCopy = newFFI(ffiOpts{ - sym: "opendal_operator_copy", +var ffiOperatorCopyWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_copy_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 @@ -219,17 +231,18 @@ var ffiOperatorCopy = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&byteSrc), unsafe.Pointer(&byteDest), + unsafe.Pointer(&token), ) 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 @@ -248,6 +261,7 @@ var ffiOperatorRename = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&byteSrc), unsafe.Pointer(&byteDest), + unsafe.Pointer(&token), ) return parseError(ctx, e) } diff --git a/bindings/go/presign.go b/bindings/go/presign.go index 4c506829ac8c..ebe62b28c506 100644 --- a/bindings/go/presign.go +++ b/bindings/go/presign.go @@ -29,41 +29,67 @@ 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)) + return op.PresignReadWithContext(context.Background(), path, expire) +} + +func (op *Operator) PresignReadWithContext(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)) + return op.PresignWriteWithContext(context.Background(), path, expire) +} + +func (op *Operator) PresignWriteWithContext(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)) + return op.PresignDeleteWithContext(context.Background(), path, expire) +} + +func (op *Operator) PresignDeleteWithContext(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)) + return op.PresignStatWithContext(context.Background(), path, expire) } -func (op *Operator) presign(path string, expire time.Duration, call presignFunc) (*http.Request, error) { - secs := uint64(expire / time.Second) +func (op *Operator) PresignStatWithContext(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { + return op.presignContext(ctx, path, expire, func() presignFunc { + return ffiOperatorPresignStatWithCancel.symbol(op.ctx) + }) +} - 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) +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) - return buildHTTPPresignedRequest(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) + }) } func buildHTTPPresignedRequest(ctx context.Context, ptr *opendalPresignedRequest) (req *http.Request, err error) { @@ -97,101 +123,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 +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 } - 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 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 181507fb2048..46544be6c3d7 100644 --- a/bindings/go/reader.go +++ b/bindings/go/reader.go @@ -61,7 +61,17 @@ 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...) + return op.ReadWithContext(context.Background(), path, opts...) +} + +func (op *Operator) ReadWithContext(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 +83,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 +94,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 } @@ -314,15 +324,25 @@ 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 + return op.ReaderWithContext(context.Background(), path) +} + +func (op *Operator) ReaderWithContext(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, + } + return reader, nil + }, func(reader *Reader) { + if reader != nil { + _ = reader.Close() + } + }) } type Reader struct { @@ -373,27 +393,33 @@ var _ io.ReadSeekCloser = (*Reader)(nil) // // Note: Always check the number of bytes read (n) as it may be less than len(buf). 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 r.ReadWithContext(context.Background(), buf) +} + +func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { + return runWithCancelContext(ctx, 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. @@ -438,7 +464,13 @@ 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. func (r *Reader) Seek(offset int64, whence int) (int64, error) { - return ffiReaderSeek.symbol(r.ctx)(r.inner, offset, whence) + return r.SeekWithContext(context.Background(), offset, whence) +} + +func (r *Reader) SeekWithContext(ctx context.Context, offset int64, whence int) (int64, error) { + return runWithCancelContext(ctx, r.ctx, func(token *opendalCancelToken) (int64, error) { + return ffiReaderSeekWithCancel.symbol(r.ctx)(r.inner, offset, whence, token) + }) } // Close releases resources associated with the OperatorReader. @@ -447,12 +479,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 @@ -462,17 +494,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 @@ -483,6 +516,7 @@ var ffiOperatorReadWith = newFFI(ffiOpts{ unsafe.Pointer(&op), unsafe.Pointer(&bytePath), unsafe.Pointer(&opts), + unsafe.Pointer(&token), ) return result.data, parseError(ctx, result.error) } @@ -594,12 +628,12 @@ var ffiReadOptionsSetGap = 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 @@ -609,6 +643,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) @@ -630,12 +665,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 @@ -647,6 +682,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) @@ -655,18 +691,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..444174a9ac8b 100644 --- a/bindings/go/stat.go +++ b/bindings/go/stat.go @@ -64,26 +64,32 @@ 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) + return op.StatWithContext(context.Background(), path, opts...) +} + +func (op *Operator) StatWithContext(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. @@ -245,15 +251,21 @@ func newOpendalStatOptions(ctx context.Context, o *statOptions) (*opendalStatOpt // fmt.Println("The file does not exist") // } func (op *Operator) IsExist(path string) (bool, error) { - return ffiOperatorIsExist.symbol(op.ctx)(op.inner, path) + return op.IsExistWithContext(context.Background(), path) +} + +func (op *Operator) IsExistWithContext(ctx context.Context, path string) (bool, error) { + return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (bool, error) { + return ffiOperatorIsExistWithCancel.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 +275,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 +284,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 +300,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,12 +309,12 @@ var ffiOperatorStatWith = newFFI(ffiOpts{ } }) -var ffiOperatorIsExist = newFFI(ffiOpts{ - sym: "opendal_operator_is_exist", +var ffiOperatorIsExistWithCancel = newFFI(ffiOpts{ + sym: "opendal_operator_is_exist_with_cancel", 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) { + 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 @@ -310,6 +324,7 @@ var ffiOperatorIsExist = newFFI(ffiOpts{ unsafe.Pointer(&result), unsafe.Pointer(&op), unsafe.Pointer(&bytePath), + unsafe.Pointer(&token), ) if result.error != nil { return false, parseError(ctx, result.error) diff --git a/bindings/go/string_ownership_test.go b/bindings/go/string_ownership_test.go index b87e9898e721..75582b96de13 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) } } } @@ -945,20 +945,20 @@ func TestReadWithOptions(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/types.go b/bindings/go/types.go index b74908a66549..be67f1f78560 100644 --- a/bindings/go/types.go +++ b/bindings/go/types.go @@ -264,6 +264,8 @@ type resultOperatorNew struct { type opendalOperator struct{} +type opendalCancelToken struct{} + type resultRead struct { data opendalBytes error *opendalError diff --git a/bindings/go/writer.go b/bindings/go/writer.go index 9c4415fe0829..78e8dde058e7 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -56,19 +56,25 @@ import ( // // 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) - } + return op.WriteWithContext(context.Background(), path, data, opts...) +} + +func (op *Operator) WriteWithContext(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. @@ -284,7 +290,13 @@ 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) + return op.CreateDirWithContext(context.Background(), path) +} + +func (op *Operator) CreateDirWithContext(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. @@ -318,8 +330,31 @@ 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) + return op.WriterWithContext(context.Background(), path, opts...) +} + +func (op *Operator) WriterWithContext(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, + } + 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 } @@ -328,24 +363,11 @@ func (op *Operator) Writer(path string, opts ...WithWriteFn) (*Writer, error) { ctx: op.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 { + ffiWriterFree.symbol(writer.ctx)(writer.inner) + } + }) } type Writer struct { @@ -381,25 +403,44 @@ 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) + return w.WriteWithContext(context.Background(), p) +} + +func (w *Writer) WriteWithContext(ctx context.Context, p []byte) (n int, err error) { + return runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (int, error) { + return ffiWriterWriteWithCancel.symbol(w.ctx)(w.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. func (w *Writer) Close() error { - defer ffiWriterFree.symbol(w.ctx)(w.inner) - return ffiWriterClose.symbol(w.ctx)(w.inner) + return w.CloseWithContext(context.Background()) +} + +func (w *Writer) CloseWithContext(ctx context.Context) error { + if ctx == nil { + ctx = context.Background() + } + if err := ctx.Err(); err != nil { + ffiWriterFree.symbol(w.ctx)(w.inner) + return err + } + return runErrWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) error { + defer ffiWriterFree.symbol(w.ctx)(w.inner) + return ffiWriterCloseWithCancel.symbol(w.ctx)(w.inner, token) + }) } 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 +452,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 +476,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 +497,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 +518,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 +527,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 +543,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 +684,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 +705,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) } diff --git a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift index 6e2bb3a7bd1c..ccd0dffbc2da 100644 --- a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift +++ b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift @@ -62,7 +62,7 @@ public class Operator { let address = dataPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) let bytes = opendal_bytes(data: address, len: UInt(dataPointer.count), capacity: UInt(dataPointer.count)) return withUnsafePointer(to: bytes) { bytesPointer in - opendal_operator_write(nativeOp, path, bytesPointer) + opendal_operator_write_with_cancel(nativeOp, path, bytesPointer, nil) } } @@ -81,7 +81,7 @@ public class Operator { } public func blockingRead(_ path: String) throws -> Data { - var ret = opendal_operator_read(nativeOp, path) + var ret = opendal_operator_read_with_cancel(nativeOp, path, nil) if let err = ret.error { defer { opendal_error_free(err) diff --git a/bindings/zig/src/opendal.zig b/bindings/zig/src/opendal.zig index 8060e57f3878..0436cd3c5a8f 100644 --- a/bindings/zig/src/opendal.zig +++ b/bindings/zig/src/opendal.zig @@ -43,14 +43,14 @@ pub const Operator = struct { .len = data.len, .capacity = data.len, }; - if (c.opendal_operator_write(self.inner, path.ptr, &bytes)) |err| { + if (c.opendal_operator_write_with_cancel(self.inner, path.ptr, &bytes, null)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn read(self: *const Operator, path: []const u8) ![]const u8 { - const result = c.opendal_operator_read(self.inner, path.ptr); + const result = c.opendal_operator_read_with_cancel(self.inner, path.ptr, null); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -59,14 +59,14 @@ pub const Operator = struct { } pub fn delete(self: *const Operator, path: []const u8) !void { - if (c.opendal_operator_delete(self.inner, path.ptr)) |err| { + if (c.opendal_operator_delete_with_cancel(self.inner, path.ptr, null)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn stat(self: *const Operator, path: []const u8) !Metadata { - const result = c.opendal_operator_stat(self.inner, path.ptr); + const result = c.opendal_operator_stat_with_cancel(self.inner, path.ptr, null); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -75,7 +75,7 @@ pub const Operator = struct { } pub fn exists(self: *const Operator, path: []const u8) !bool { - const result = c.opendal_operator_exists(self.inner, path.ptr); + const result = c.opendal_operator_exists_with_cancel(self.inner, path.ptr, null); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -84,7 +84,7 @@ pub const Operator = struct { } pub fn list(self: *const Operator, path: []const u8) !Lister { - const result = c.opendal_operator_list(self.inner, path.ptr); + const result = c.opendal_operator_list_with_cancel(self.inner, path.ptr, null); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -93,21 +93,21 @@ pub const Operator = struct { } pub fn createDir(self: *const Operator, path: []const u8) !void { - if (c.opendal_operator_create_dir(self.inner, path.ptr)) |err| { + if (c.opendal_operator_create_dir_with_cancel(self.inner, path.ptr, null)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn rename(self: *const Operator, src: []const u8, dest: []const u8) !void { - if (c.opendal_operator_rename(self.inner, src.ptr, dest.ptr)) |err| { + if (c.opendal_operator_rename_with_cancel(self.inner, src.ptr, dest.ptr, null)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn copy(self: *const Operator, src: []const u8, dest: []const u8) !void { - if (c.opendal_operator_copy(self.inner, src.ptr, dest.ptr)) |err| { + if (c.opendal_operator_copy_with_cancel(self.inner, src.ptr, dest.ptr, null)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } @@ -162,7 +162,7 @@ pub const Lister = struct { } pub fn next(self: *const Lister) !?Entry { - const result = c.opendal_lister_next(self.inner); + const result = c.opendal_lister_next_with_cancel(self.inner, null); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); diff --git a/bindings/zig/test/bdd.zig b/bindings/zig/test/bdd.zig index 8d2f6ce79f6c..2046f1447f26 100644 --- a/bindings/zig/test/bdd.zig +++ b/bindings/zig/test/bdd.zig @@ -65,16 +65,16 @@ test "Opendal BDD test" { .len = dupe_content.len, .capacity = dupe_content.len, }; - const result = opendal.c.opendal_operator_write(testkit.p, testkit.path, &data); + const result = opendal.c.opendal_operator_write_with_cancel(testkit.p, testkit.path, &data, null); try testing.expectEqual(result, null); // The blocking file "test" should exist - const e: opendal.c.opendal_result_is_exist = opendal.c.opendal_operator_is_exist(testkit.p, testkit.path); + const e: opendal.c.opendal_result_is_exist = opendal.c.opendal_operator_is_exist_with_cancel(testkit.p, testkit.path, null); try testing.expectEqual(e.@"error", null); try testing.expect(e.is_exist); // The blocking file "test" entry mode must be file - const s: opendal.c.opendal_result_stat = opendal.c.opendal_operator_stat(testkit.p, testkit.path); + const s: opendal.c.opendal_result_stat = opendal.c.opendal_operator_stat_with_cancel(testkit.p, testkit.path, null); try testing.expectEqual(s.@"error", null); const meta: [*c]opendal.c.opendal_metadata = s.meta; try testing.expect(opendal.c.opendal_metadata_is_file(meta)); @@ -84,7 +84,7 @@ test "Opendal BDD test" { defer opendal.c.opendal_metadata_free(meta); // The blocking file "test" must have content "Hello, World!" - var r: opendal.c.opendal_result_read = opendal.c.opendal_operator_read(testkit.p, testkit.path); + var r: opendal.c.opendal_result_read = opendal.c.opendal_operator_read_with_cancel(testkit.p, testkit.path, null); defer opendal.c.opendal_bytes_free(&r.data); try testing.expect(r.@"error" == null); try testing.expectEqual(std.mem.len(testkit.content), r.data.len); From 091dd77e214664c4e5a63d9d29a43b16db5273ad Mon Sep 17 00:00:00 2001 From: dentiny Date: Mon, 8 Jun 2026 19:56:42 +0000 Subject: [PATCH 02/13] keep old API --- bindings/c/include/opendal.h | 123 +++++++++++++++++++++++++ bindings/c/src/lister.rs | 6 ++ bindings/c/src/operator.rs | 172 +++++++++++++++++++++++++++++++++++ bindings/c/src/presign.rs | 36 ++++++++ bindings/c/src/reader.rs | 20 ++++ bindings/c/src/writer.rs | 15 +++ 6 files changed, 372 insertions(+) diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 337ecd9c50dd..14652202b8e7 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -1094,6 +1094,11 @@ void opendal_cancel_token_free(struct opendal_cancel_token *ptr); 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. + */ +struct opendal_result_lister_next opendal_lister_next(struct opendal_lister *self); + /** * \brief Free the heap-allocated metadata used by opendal_lister */ @@ -1369,6 +1374,7 @@ struct opendal_result_operator_new opendal_operator_new_with_layers(const char * * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Write raw bytes to `path` with cancellation support. */ struct opendal_error *opendal_operator_write_with_cancel(const struct opendal_operator *op, @@ -1425,6 +1431,7 @@ struct opendal_error *opendal_operator_write_with_options_cancel(const struct op * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Read data from `path` with cancellation support. */ struct opendal_result_read opendal_operator_read_with_cancel(const struct opendal_operator *op, @@ -1476,6 +1483,7 @@ struct opendal_result_read opendal_operator_read_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Create a reader with cancellation support. */ struct opendal_result_operator_reader opendal_operator_reader_with_cancel(const struct opendal_operator *op, @@ -1519,6 +1527,7 @@ struct opendal_result_operator_reader opendal_operator_reader_with_cancel(const * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Create a writer with cancellation support. */ struct opendal_result_operator_writer opendal_operator_writer_with_cancel(const struct opendal_operator *op, @@ -1574,6 +1583,7 @@ struct opendal_result_operator_writer opendal_operator_writer_with_options_cance * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Delete the object in `path` with cancellation support. */ struct opendal_error *opendal_operator_delete_with_cancel(const struct opendal_operator *op, @@ -1626,6 +1636,7 @@ struct opendal_error *opendal_operator_delete_with_options_cancel(const struct o * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Check whether the path exists with cancellation support. */ struct opendal_result_is_exist opendal_operator_is_exist_with_cancel(const struct opendal_operator *op, @@ -1670,6 +1681,7 @@ struct opendal_result_is_exist opendal_operator_is_exist_with_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Check whether the path exists with cancellation support. */ struct opendal_result_exists opendal_operator_exists_with_cancel(const struct opendal_operator *op, @@ -1713,6 +1725,7 @@ struct opendal_result_exists opendal_operator_exists_with_cancel(const struct op * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Stat the path with cancellation support. */ struct opendal_result_stat opendal_operator_stat_with_cancel(const struct opendal_operator *op, @@ -1776,6 +1789,7 @@ struct opendal_result_stat opendal_operator_stat_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief List the objects in `path` with cancellation support. */ struct opendal_result_list opendal_operator_list_with_cancel(const struct opendal_operator *op, @@ -1824,6 +1838,7 @@ struct opendal_result_list opendal_operator_list_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information + * * \brief Create the directory in `path` with cancellation support. */ struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opendal_operator *op, @@ -1872,6 +1887,7 @@ struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opend * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information + * * \brief Rename the object with cancellation support. */ struct opendal_error *opendal_operator_rename_with_cancel(const struct opendal_operator *op, @@ -1921,6 +1937,7 @@ struct opendal_error *opendal_operator_rename_with_cancel(const struct opendal_o * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information + * * \brief Copy the object with cancellation support. */ struct opendal_error *opendal_operator_copy_with_cancel(const struct opendal_operator *op, @@ -1931,6 +1948,71 @@ struct opendal_error *opendal_operator_copy_with_cancel(const struct opendal_ope struct opendal_error *opendal_operator_check_with_cancel(const struct opendal_operator *op, const struct opendal_cancel_token *token); +struct opendal_error *opendal_operator_write(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes); + +struct opendal_error *opendal_operator_write_with(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes, + const struct opendal_write_options *opts); + +struct opendal_result_read opendal_operator_read(const struct opendal_operator *op, + const char *path); + +struct opendal_result_read opendal_operator_read_with(const struct opendal_operator *op, + const char *path, + const struct opendal_read_options *opts); + +struct opendal_result_operator_reader opendal_operator_reader(const struct opendal_operator *op, + const char *path); + +struct opendal_result_operator_writer opendal_operator_writer(const struct opendal_operator *op, + const char *path); + +struct opendal_result_operator_writer opendal_operator_writer_with(const struct opendal_operator *op, + const char *path, + const struct opendal_write_options *opts); + +struct opendal_error *opendal_operator_delete(const struct opendal_operator *op, const char *path); + +struct opendal_error *opendal_operator_delete_with(const struct opendal_operator *op, + const char *path, + const struct opendal_delete_options *opts); + +struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_operator *op, + const char *path); + +struct opendal_result_exists opendal_operator_exists(const struct opendal_operator *op, + const char *path); + +struct opendal_result_stat opendal_operator_stat(const struct opendal_operator *op, + const char *path); + +struct opendal_result_stat opendal_operator_stat_with(const struct opendal_operator *op, + const char *path, + const struct opendal_stat_options *opts); + +struct opendal_result_list opendal_operator_list(const struct opendal_operator *op, + const char *path); + +struct opendal_result_list opendal_operator_list_with(const struct opendal_operator *op, + const char *path, + const struct opendal_list_options *opts); + +struct opendal_error *opendal_operator_create_dir(const struct opendal_operator *op, + const char *path); + +struct opendal_error *opendal_operator_rename(const struct opendal_operator *op, + const char *src, + const char *dest); + +struct opendal_error *opendal_operator_copy(const struct opendal_operator *op, + const char *src, + const char *dest); + +struct opendal_error *opendal_operator_check(const struct opendal_operator *op); + /** * \brief Get information of underlying accessor. * @@ -2020,6 +2102,22 @@ struct opendal_result_presign opendal_operator_presign_stat_with_cancel(const st uint64_t expire_secs, const struct opendal_cancel_token *token); +struct opendal_result_presign opendal_operator_presign_read(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs); + +struct opendal_result_presign opendal_operator_presign_write(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs); + +struct opendal_result_presign opendal_operator_presign_delete(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs); + +struct opendal_result_presign opendal_operator_presign_stat(const struct opendal_operator *op, + const char *path, + uint64_t expire_secs); + /** * Get the method of the presigned request. */ @@ -2434,6 +2532,13 @@ struct opendal_result_reader_read opendal_reader_read_with_cancel(struct opendal uintptr_t len, const struct opendal_cancel_token *token); +/** + * \brief Read data from the reader. + */ +struct opendal_result_reader_read opendal_reader_read(struct opendal_reader *self, + uint8_t *buf, + uintptr_t len); + /** * \brief Seek to an offset with cancellation support. */ @@ -2442,6 +2547,13 @@ struct opendal_result_reader_seek opendal_reader_seek_with_cancel(struct opendal int32_t whence, const struct opendal_cancel_token *token); +/** + * \brief Seek to an offset, in bytes, in a stream. + */ +struct opendal_result_reader_seek opendal_reader_seek(struct opendal_reader *self, + int64_t offset, + int32_t whence); + /** * \brief Frees the heap memory used by the opendal_reader. */ @@ -2454,12 +2566,23 @@ struct opendal_result_writer_write opendal_writer_write_with_cancel(struct opend 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 Close the writer with cancellation support. */ 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. + */ +struct opendal_error *opendal_writer_close(struct opendal_writer *ptr); + /** * \brief Frees the heap memory used by the opendal_writer. */ diff --git a/bindings/c/src/lister.rs b/bindings/c/src/lister.rs index a16e6641dd9b..0a8334033362 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -89,6 +89,12 @@ impl opendal_lister { })) } + /// \brief Return the next object to be listed. + #[no_mangle] + pub unsafe extern "C" fn opendal_lister_next(&mut self) -> opendal_result_lister_next { + unsafe { Self::opendal_lister_next_with_cancel(self, std::ptr::null()) } + } + /// \brief Free the heap-allocated metadata used by opendal_lister #[no_mangle] pub unsafe extern "C" fn opendal_lister_free(ptr: *mut opendal_lister) { diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index e3a1682131e6..cce64072afbc 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -346,6 +346,7 @@ pub unsafe extern "C" fn opendal_operator_new_with_layers( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Write raw bytes to `path` with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_write_with_cancel( @@ -423,6 +424,7 @@ pub unsafe extern "C" fn opendal_operator_write_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Read data from `path` with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_read_with_cancel( @@ -494,6 +496,7 @@ pub unsafe extern "C" fn opendal_operator_read_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Create a reader with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_reader_with_cancel( @@ -551,6 +554,7 @@ pub unsafe extern "C" fn opendal_operator_reader_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Create a writer with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_writer_with_cancel( @@ -639,6 +643,7 @@ pub unsafe extern "C" fn opendal_operator_writer_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Delete the object in `path` with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_delete_with_cancel( @@ -707,6 +712,7 @@ pub unsafe extern "C" fn opendal_operator_delete_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Check whether the path exists with cancellation support. #[no_mangle] #[cfg_attr(cbindgen, cbindgen::ignore)] @@ -766,6 +772,7 @@ pub unsafe extern "C" fn opendal_operator_is_exist_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Check whether the path exists with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_exists_with_cancel( @@ -823,6 +830,7 @@ pub unsafe extern "C" fn opendal_operator_exists_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Stat the path with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_stat_with_cancel( @@ -906,6 +914,7 @@ pub unsafe extern "C" fn opendal_operator_stat_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief List the objects in `path` with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_list_with_cancel( @@ -983,6 +992,7 @@ pub unsafe extern "C" fn opendal_operator_list_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information +/// /// \brief Create the directory in `path` with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( @@ -1038,6 +1048,7 @@ pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information +/// /// \brief Rename the object with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_rename_with_cancel( @@ -1095,6 +1106,7 @@ pub unsafe extern "C" fn opendal_operator_rename_with_cancel( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information +/// /// \brief Copy the object with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_operator_copy_with_cancel( @@ -1119,3 +1131,163 @@ pub unsafe extern "C" fn opendal_operator_check_with_cancel( let op = op.deref().clone(); result_error(block_on_cancelable(token, async move { op.check().await })) } + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_write( + op: &opendal_operator, + path: *const c_char, + bytes: &opendal_bytes, +) -> *mut opendal_error { + unsafe { opendal_operator_write_with_cancel(op, path, bytes, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_write_with( + op: &opendal_operator, + path: *const c_char, + bytes: &opendal_bytes, + opts: *const opendal_write_options, +) -> *mut opendal_error { + unsafe { opendal_operator_write_with_options_cancel(op, path, bytes, opts, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_read( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_read { + unsafe { opendal_operator_read_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_read_with( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_read_options, +) -> opendal_result_read { + unsafe { opendal_operator_read_with_options_cancel(op, path, opts, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_reader( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_operator_reader { + unsafe { opendal_operator_reader_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_writer( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_operator_writer { + unsafe { opendal_operator_writer_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_writer_with( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_write_options, +) -> opendal_result_operator_writer { + unsafe { opendal_operator_writer_with_options_cancel(op, path, opts, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_delete( + op: &opendal_operator, + path: *const c_char, +) -> *mut opendal_error { + unsafe { opendal_operator_delete_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_delete_with( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_delete_options, +) -> *mut opendal_error { + unsafe { opendal_operator_delete_with_options_cancel(op, path, opts, std::ptr::null()) } +} + +#[no_mangle] +#[cfg_attr(cbindgen, cbindgen::ignore)] +pub unsafe extern "C" fn opendal_operator_is_exist( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_is_exist { + unsafe { opendal_operator_is_exist_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_exists( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_exists { + unsafe { opendal_operator_exists_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_stat( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_stat { + unsafe { opendal_operator_stat_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_stat_with( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_stat_options, +) -> opendal_result_stat { + unsafe { opendal_operator_stat_with_options_cancel(op, path, opts, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_list( + op: &opendal_operator, + path: *const c_char, +) -> opendal_result_list { + unsafe { opendal_operator_list_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_list_with( + op: &opendal_operator, + path: *const c_char, + opts: *const opendal_list_options, +) -> opendal_result_list { + unsafe { opendal_operator_list_with_options_cancel(op, path, opts, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_create_dir( + op: &opendal_operator, + path: *const c_char, +) -> *mut opendal_error { + unsafe { opendal_operator_create_dir_with_cancel(op, path, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_rename( + op: &opendal_operator, + src: *const c_char, + dest: *const c_char, +) -> *mut opendal_error { + unsafe { opendal_operator_rename_with_cancel(op, src, dest, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_copy( + op: &opendal_operator, + src: *const c_char, + dest: *const c_char, +) -> *mut opendal_error { + unsafe { opendal_operator_copy_with_cancel(op, src, dest, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_check(op: &opendal_operator) -> *mut opendal_error { + 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 1be9b2238c39..4c1afcb2db59 100644 --- a/bindings/c/src/presign.rs +++ b/bindings/c/src/presign.rs @@ -214,6 +214,42 @@ pub unsafe extern "C" fn opendal_operator_presign_stat_with_cancel( })) } +#[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()) } +} + +#[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()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_delete( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, +) -> opendal_result_presign { + unsafe { opendal_operator_presign_delete_with_cancel(op, path, expire_secs, std::ptr::null()) } +} + +#[no_mangle] +pub unsafe extern "C" fn opendal_operator_presign_stat( + op: &opendal_operator, + path: *const c_char, + expire_secs: u64, +) -> opendal_result_presign { + unsafe { opendal_operator_presign_stat_with_cancel(op, path, expire_secs, std::ptr::null()) } +} + /// Get the method of the presigned request. #[no_mangle] pub unsafe extern "C" fn opendal_presigned_request_method( diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index 73d4e3066554..fc9e0b0b3837 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -112,6 +112,16 @@ impl opendal_reader { })) } + /// \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 Seek to an offset with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_reader_seek_with_cancel( @@ -136,6 +146,16 @@ impl opendal_reader { } } + /// \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) { diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs index 0fd507365c74..dbe537c8615e 100644 --- a/bindings/c/src/writer.rs +++ b/bindings/c/src/writer.rs @@ -84,6 +84,15 @@ impl opendal_writer { })) } + /// \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 Close the writer with cancellation support. #[no_mangle] pub unsafe extern "C" fn opendal_writer_close_with_cancel( @@ -103,6 +112,12 @@ impl opendal_writer { } } + /// \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) { From 7cc9cf5f8033c423b89ab9286a2e6a276cebb237 Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 06:58:32 +0000 Subject: [PATCH 03/13] real cancellation --- bindings/c/include/opendal.h | 4 +- bindings/c/src/operator.rs | 7 ++- bindings/c/src/reader.rs | 87 ++++++++++++++-------------- bindings/go/README.md | 8 +++ bindings/go/context_test.go | 106 +++++++++++++++++++++++++++++------ bindings/go/ffi.go | 55 ++++++++++-------- bindings/go/reader.go | 11 +++- bindings/go/writer.go | 54 ++++++++++++++++-- 8 files changed, 235 insertions(+), 97 deletions(-) diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 14652202b8e7..7ad54d0de970 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -497,11 +497,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; diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index cce64072afbc..8767ff4059bd 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -506,9 +506,12 @@ pub unsafe extern "C" fn opendal_operator_reader_with_cancel( ) -> opendal_result_operator_reader { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.reader(&path).await }) { + match block_on_cancelable(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::new_async(reader))), + reader: Box::into_raw(Box::new(opendal_reader::from_async(reader))), error: std::ptr::null_mut(), }, Err(e) => opendal_result_operator_reader { diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index fc9e0b0b3837..87e4a27b3623 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -17,8 +17,11 @@ use std::ffi::c_void; use std::future::Future; +use std::io::SeekFrom; use ::opendal as core; +use futures_util::AsyncReadExt; +use futures_util::AsyncSeekExt; use crate::operator::RUNTIME; use crate::result::opendal_result_reader_seek; @@ -32,17 +35,16 @@ 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, } -struct AsyncReader { - reader: core::Reader, - position: u64, +pub(crate) struct AsyncReader { + reader: core::FuturesAsyncReader, } impl opendal_reader { @@ -54,12 +56,14 @@ impl opendal_reader { } impl opendal_reader { - pub(crate) fn new_async(reader: core::Reader) -> 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(AsyncReader { - reader, - position: 0, - })) as _, + inner: Box::into_raw(Box::new(reader)) as _, } } @@ -72,15 +76,11 @@ impl opendal_reader { } async fn read_async_inner(reader: &mut AsyncReader, buf: &mut [u8]) -> core::Result { - let len = buf.len() as u64; - let data = reader + reader .reader - .read(reader.position..reader.position + len) - .await?; - let size = data.len().min(buf.len()); - buf[..size].copy_from_slice(&data.to_vec()[..size]); - reader.position += size as u64; - Ok(size) + .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 { @@ -131,10 +131,9 @@ impl opendal_reader { token: *const opendal_cancel_token, ) -> opendal_result_reader_seek { let reader = self.deref_mut(); - match Self::block_on_cancelable( - token, - async move { seek_async_reader(reader, offset, whence) }, - ) { + match Self::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(), @@ -168,19 +167,20 @@ impl opendal_reader { } } -fn seek_async_reader(reader: &mut AsyncReader, offset: i64, whence: i32) -> core::Result { - let base = match whence { - _x @ OPENDAL_SEEK_SET => 0, - _x @ OPENDAL_SEEK_CUR => reader.position as i64, - _x @ OPENDAL_SEEK_END => { - let Some(meta) = reader.reader.metadata() else { - return Err(core::Error::new( - core::ErrorKind::Unsupported, - "seek from end requires reader metadata", - )); - }; - meta.content_length() as i64 - } +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, @@ -188,15 +188,10 @@ fn seek_async_reader(reader: &mut AsyncReader, offset: i64, whence: i32) -> core )); } }; - let position = base.checked_add(offset).ok_or_else(|| { - core::Error::new(core::ErrorKind::Unexpected, "reader seek position overflow") - })?; - if position < 0 { - return Err(core::Error::new( - core::ErrorKind::Unexpected, - "reader seek position is negative", - )); - } - reader.position = position as u64; - Ok(reader.position) + + reader + .reader + .seek(pos) + .await + .map_err(|err| core::Error::new(core::ErrorKind::Unexpected, err.to_string())) } diff --git a/bindings/go/README.md b/bindings/go/README.md index ecdcb2f86e75..03ec2161d869 100644 --- a/bindings/go/README.md +++ b/bindings/go/README.md @@ -88,6 +88,14 @@ func main() { } ``` +## Context Cancellation + +Methods ending in `WithContext` forward context cancellation to OpenDAL's native +cancel token. When the Go context is canceled, the binding signals cancellation +and returns `context.Canceled` or `context.DeadlineExceeded` immediately. The +native operation may continue briefly in the background until it observes the +cancel token. + ## Run Tests ### Behavior Tests diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index 4915da1ae04f..bbfa53b74bb9 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -86,6 +86,96 @@ func TestOperatorWithContextMethodsReturnCanceledContext(t *testing.T) { } } +func TestCancellableReadBufferUsesHeapForCancelableContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + buf := make([]byte, 8) + readBuf, apply := cancellableReadBuffer(ctx, buf) + if len(readBuf) != len(buf) { + t.Fatalf("readBuf len = %d, want %d", len(readBuf), len(buf)) + } + if &readBuf[0] == &buf[0] { + t.Fatal("expected heap buffer for cancelable context") + } + + readBuf[0] = 'x' + apply(1) + if buf[0] != 'x' { + t.Fatalf("buf[0] = %q, want %q", buf[0], 'x') + } +} + +func TestCancellableReadBufferReusesCallerBufferForBackgroundContext(t *testing.T) { + buf := make([]byte, 8) + readBuf, apply := cancellableReadBuffer(context.Background(), buf) + if &readBuf[0] != &buf[0] { + t.Fatal("expected caller buffer for non-cancelable context") + } + apply(0) +} + +func TestCancellableWriteBufferCopiesForCancelableContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + data := []byte("hello") + copied := cancellableWriteBuffer(ctx, data) + if &copied[0] == &data[0] { + t.Fatal("expected heap copy for cancelable context") + } + copied[0] = 'H' + if data[0] != 'h' { + t.Fatalf("data[0] = %q, want %q", data[0], 'h') + } +} + +func TestWriterCloseWithContextPreCancelledPreservesHandle(t *testing.T) { + inner := &opendalWriter{} + w := &Writer{inner: inner} + + if err := w.CloseWithContext(newCanceledContext()); !errors.Is(err, context.Canceled) { + t.Fatalf("CloseWithContext() 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.CloseWithContext(context.Background()); err != nil { + t.Fatalf("second CloseWithContext() = %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 TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { + inner := &opendalWriter{} + w := &Writer{inner: inner} + + if err := w.CloseWithContext(newCanceledContext()); !errors.Is(err, context.Canceled) { + t.Fatalf("CloseWithContext() error = %v, want context.Canceled", err) + } + if w.inner != inner { + t.Fatal("pre-cancelled close should preserve writer handle for deferred close") + } +} + func TestIOWithContextMethodsReturnCanceledContext(t *testing.T) { ctx := newCanceledContext() @@ -110,19 +200,3 @@ func TestIOWithContextMethodsReturnCanceledContext(t *testing.T) { t.Fatalf("Lister.Error() = %v, want context.Canceled", lister.Error()) } } - -func TestRunWithContextReturnsDeadlineExceeded(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) - defer cancel() - - done := make(chan struct{}) - defer close(done) - - _, err := runWithContext(ctx, func() (struct{}, error) { - <-done - return struct{}{}, nil - }) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("runWithContext() error = %v, want context.DeadlineExceeded", err) - } -} diff --git a/bindings/go/ffi.go b/bindings/go/ffi.go index da298c5eb637..11cc515c4a55 100644 --- a/bindings/go/ffi.go +++ b/bindings/go/ffi.go @@ -41,17 +41,13 @@ type contextResult[T any] struct { err error } -func runWithContext[T any](ctx context.Context, fn func() (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 - } - return fn() -} - +// runWithCancelContext forwards Go context cancellation to native OpenDAL calls. +// +// When the context is canceled, the native cancel token is signaled and this +// helper returns ctx.Err() immediately. The native call may continue briefly in +// the background until it observes cancellation. Callers that pass slice data +// across FFI must use cancellableReadBuffer or cancellableWriteBuffer so the +// background call does not touch caller-owned memory after this returns. 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 { @@ -65,10 +61,10 @@ func runWithCancelContext[T any](ctx context.Context, ffiCtx context.Context, fn } token := ffiCancelTokenNew.symbol(ffiCtx)() - defer ffiCancelTokenFree.symbol(ffiCtx)(token) ch := make(chan contextResult[T], 1) go func() { + defer ffiCancelTokenFree.symbol(ffiCtx)(token) value, err := fn(token) ch <- contextResult[T]{value: value, err: err} }() @@ -78,21 +74,36 @@ func runWithCancelContext[T any](ctx context.Context, ffiCtx context.Context, fn return result.value, result.err case <-ctx.Done(): ffiCancelTokenCancel.symbol(ffiCtx)(token) - result := <-ch - for _, cleanup := range cleanup { - if cleanup != nil { - cleanup(result.value) + go func() { + result := <-ch + for _, cleanupFn := range cleanup { + if cleanupFn != nil { + cleanupFn(result.value) + } } - } + }() return zero, ctx.Err() } } -func runErrWithContext(ctx context.Context, fn func() error) error { - _, err := runWithContext(ctx, func() (struct{}, error) { - return struct{}{}, fn() - }) - return err +func cancellableReadBuffer(ctx context.Context, buf []byte) (readBuf []byte, apply func(int)) { + if ctx.Done() == nil || len(buf) == 0 { + return buf, func(int) {} + } + + heap := make([]byte, len(buf)) + return heap, func(n int) { + if n > 0 { + copy(buf, heap[:n]) + } + } +} + +func cancellableWriteBuffer(ctx context.Context, data []byte) []byte { + if ctx.Done() == nil || len(data) == 0 { + return data + } + return append([]byte(nil), data...) } func runErrWithCancelContext(ctx context.Context, ffiCtx context.Context, fn func(*opendalCancelToken) error) error { diff --git a/bindings/go/reader.go b/bindings/go/reader.go index 46544be6c3d7..244de21025a4 100644 --- a/bindings/go/reader.go +++ b/bindings/go/reader.go @@ -397,8 +397,9 @@ func (r *Reader) Read(buf []byte) (int, error) { } func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { - return runWithCancelContext(ctx, r.ctx, func(token *opendalCancelToken) (int, error) { - length := uint(len(buf)) + readBuf, apply := cancellableReadBuffer(ctx, buf) + n, err := runWithCancelContext(ctx, r.ctx, func(token *opendalCancelToken) (int, error) { + length := uint(len(readBuf)) if length == 0 { return 0, nil } @@ -409,7 +410,7 @@ func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { err error ) for { - size, err = read(r.inner, buf[totalSize:], token) + size, err = read(r.inner, readBuf[totalSize:], token) totalSize += size if size == 0 || err != nil || totalSize >= length { break @@ -420,6 +421,10 @@ func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { } return int(totalSize), err }) + if err == nil { + apply(n) + } + return n, err } // Seek sets the offset for the next Read operation on the reader. diff --git a/bindings/go/writer.go b/bindings/go/writer.go index 78e8dde058e7..a35cf2653b61 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`. @@ -60,6 +63,7 @@ func (op *Operator) Write(path string, data []byte, opts ...WithWriteFn) error { } func (op *Operator) WriteWithContext(ctx context.Context, path string, data []byte, opts ...WithWriteFn) error { + data = cancellableWriteBuffer(ctx, data) return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { if len(opts) == 0 { return ffiOperatorWriteWithCancel.symbol(op.ctx)(op.inner, path, data, token) @@ -365,7 +369,7 @@ func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ... return writer, nil }, func(writer *Writer) { if writer != nil { - ffiWriterFree.symbol(writer.ctx)(writer.inner) + writer.free() } }) } @@ -373,6 +377,18 @@ func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ... type Writer struct { inner *opendalWriter ctx context.Context + mu sync.Mutex +} + +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. @@ -407,8 +423,23 @@ func (w *Writer) Write(p []byte) (n int, err error) { } func (w *Writer) WriteWithContext(ctx context.Context, p []byte) (n int, err error) { + if ctx == nil { + ctx = context.Background() + } + if err := ctx.Err(); err != nil { + return 0, err + } + + w.mu.Lock() + inner := w.inner + w.mu.Unlock() + if inner == nil { + return 0, errWriterClosed + } + + p = cancellableWriteBuffer(ctx, p) return runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (int, error) { - return ffiWriterWriteWithCancel.symbol(w.ctx)(w.inner, p, token) + return ffiWriterWriteWithCancel.symbol(w.ctx)(inner, p, token) }) } @@ -424,13 +455,24 @@ func (w *Writer) CloseWithContext(ctx context.Context) error { ctx = context.Background() } if err := ctx.Err(); err != nil { - ffiWriterFree.symbol(w.ctx)(w.inner) return err } - return runErrWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) error { - defer ffiWriterFree.symbol(w.ctx)(w.inner) - return ffiWriterCloseWithCancel.symbol(w.ctx)(w.inner, token) + + 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) { + closeErr := ffiWriterCloseWithCancel.symbol(w.ctx)(inner, token) + ffiWriterFree.symbol(w.ctx)(inner) + return struct{}{}, closeErr }) + return err } var _ io.WriteCloser = (*Writer)(nil) From 7bb2c2d9ab5cbfdeea21ba0052cb1cdcf548ae4b Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 07:30:21 +0000 Subject: [PATCH 04/13] revert unnecessary change --- bindings/c/README.md | 4 +- bindings/c/examples/basic.c | 4 +- bindings/c/examples/error_handle.c | 2 +- bindings/c/tests/bdd.cpp | 32 ++--- bindings/c/tests/error_msg.cpp | 2 +- bindings/c/tests/example_test.cpp | 8 +- bindings/c/tests/list.cpp | 12 +- bindings/c/tests/reader.cpp | 16 +-- bindings/c/tests/test_runner.cpp | 2 +- bindings/c/tests/test_suites_basic.cpp | 40 +++--- bindings/c/tests/test_suites_list.cpp | 114 +++++++++--------- bindings/c/tests/test_suites_presign.cpp | 28 ++--- .../c/tests/test_suites_reader_writer.cpp | 62 +++++----- bindings/d/source/opendal/operator.d | 20 +-- .../OpenDAL/Sources/OpenDAL/Operator.swift | 4 +- bindings/zig/src/opendal.zig | 20 +-- bindings/zig/test/bdd.zig | 8 +- 17 files changed, 189 insertions(+), 189 deletions(-) diff --git a/bindings/c/README.md b/bindings/c/README.md index a3697fb68372..c4ad228fa0bd 100644 --- a/bindings/c/README.md +++ b/bindings/c/README.md @@ -31,11 +31,11 @@ int main() }; /* Write this into path "/testpath" */ - opendal_error *error = opendal_operator_write_with_cancel(op, "/testpath", &data, NULL); + opendal_error *error = opendal_operator_write(op, "/testpath", &data); assert(error == NULL); /* We can read it out, make sure the data is the same */ - opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); + opendal_result_read r = opendal_operator_read(op, "/testpath"); opendal_bytes read_bytes = r.data; assert(r.error == NULL); assert(read_bytes.len == 24); diff --git a/bindings/c/examples/basic.c b/bindings/c/examples/basic.c index ee5bb4da14eb..199403ca53b0 100644 --- a/bindings/c/examples/basic.c +++ b/bindings/c/examples/basic.c @@ -37,11 +37,11 @@ int main() }; /* Write this into path "/testpath" */ - opendal_error* error = opendal_operator_write_with_cancel(op, "/testpath", &data, NULL); + opendal_error* error = opendal_operator_write(op, "/testpath", &data); assert(error == NULL); /* We can read it out, make sure the data is the same */ - opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); + opendal_result_read r = opendal_operator_read(op, "/testpath"); opendal_bytes read_bytes = r.data; assert(r.error == NULL); assert(read_bytes.len == 24); diff --git a/bindings/c/examples/error_handle.c b/bindings/c/examples/error_handle.c index 1fc73ff7f329..193a788afa97 100644 --- a/bindings/c/examples/error_handle.c +++ b/bindings/c/examples/error_handle.c @@ -32,7 +32,7 @@ int main() opendal_operator* op = result.op; /* The read is supposed to fail */ - opendal_result_read r = opendal_operator_read_with_cancel(op, "/testpath", NULL); + opendal_result_read r = opendal_operator_read(op, "/testpath"); assert(r.error != NULL); assert(r.error->code == OPENDAL_NOT_FOUND); diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp index 25cbb2f48e54..946df0c86630 100644 --- a/bindings/c/tests/bdd.cpp +++ b/bindings/c/tests/bdd.cpp @@ -61,19 +61,19 @@ TEST_F(OpendalBddTest, FeatureTest) .data = (uint8_t*)this->content.c_str(), .len = this->content.length(), }; - opendal_error* error = opendal_operator_check_with_cancel(this->p, nullptr); + opendal_error* error = opendal_operator_check(this->p); EXPECT_EQ(error, nullptr); - error = opendal_operator_write_with_cancel(this->p, this->path.c_str(), &data, nullptr); + error = opendal_operator_write(this->p, this->path.c_str(), &data); EXPECT_EQ(error, nullptr); // The blocking file "test" should exist - opendal_result_exists e = opendal_operator_exists_with_cancel(this->p, this->path.c_str(), nullptr); + opendal_result_exists e = opendal_operator_exists(this->p, this->path.c_str()); EXPECT_EQ(e.error, nullptr); EXPECT_TRUE(e.exists); // The blocking file "test" entry mode must be file - opendal_result_stat s = opendal_operator_stat_with_cancel(this->p, this->path.c_str(), nullptr); + opendal_result_stat s = opendal_operator_stat(this->p, this->path.c_str()); EXPECT_EQ(s.error, nullptr); opendal_metadata* meta = s.meta; EXPECT_TRUE(opendal_metadata_is_file(meta)); @@ -86,7 +86,7 @@ TEST_F(OpendalBddTest, FeatureTest) opendal_metadata_free(meta); // The blocking file "test" must have content "Hello, World!" - struct opendal_result_read r = opendal_operator_read_with_cancel(this->p, this->path.c_str(), nullptr); + struct opendal_result_read r = opendal_operator_read(this->p, this->path.c_str()); EXPECT_EQ(r.error, nullptr); EXPECT_EQ(r.data.len, this->content.length()); for (int i = 0; i < r.data.len; i++) { @@ -94,27 +94,27 @@ TEST_F(OpendalBddTest, FeatureTest) } // The blocking file should be deleted - error = opendal_operator_delete_with_cancel(this->p, this->path.c_str(), nullptr); + error = opendal_operator_delete(this->p, this->path.c_str()); EXPECT_EQ(error, nullptr); - e = opendal_operator_exists_with_cancel(this->p, this->path.c_str(), nullptr); + e = opendal_operator_exists(this->p, this->path.c_str()); EXPECT_EQ(e.error, nullptr); EXPECT_FALSE(e.exists); - opendal_result_operator_writer writer = opendal_operator_writer_with_cancel(this->p, this->path.c_str(), nullptr); + opendal_result_operator_writer writer = opendal_operator_writer(this->p, this->path.c_str()); EXPECT_EQ(writer.error, nullptr); - opendal_result_writer_write w = opendal_writer_write_with_cancel(writer.writer, &data, nullptr); + opendal_result_writer_write w = opendal_writer_write(writer.writer, &data); EXPECT_EQ(w.error, nullptr); EXPECT_EQ(w.size, this->content.length()); - opendal_error* close_err = opendal_writer_close_with_cancel(writer.writer, nullptr); + opendal_error* close_err = opendal_writer_close(writer.writer); EXPECT_EQ(close_err, nullptr); opendal_writer_free(writer.writer); // The blocking file "test" must have content "Hello, World!" and read into buffer int length = this->content.length(); unsigned char buffer[this->content.length()]; - opendal_result_operator_reader reader = opendal_operator_reader_with_cancel(this->p, this->path.c_str(), nullptr); + opendal_result_operator_reader reader = opendal_operator_reader(this->p, this->path.c_str()); EXPECT_EQ(reader.error, nullptr); - auto rst = opendal_reader_read_with_cancel(reader.reader, buffer, length, nullptr); + auto rst = opendal_reader_read(reader.reader, buffer, length); EXPECT_EQ(rst.size, length); for (int i = 0; i < this->content.length(); i++) { EXPECT_EQ(this->content[i], buffer[i]); @@ -122,19 +122,19 @@ TEST_F(OpendalBddTest, FeatureTest) opendal_reader_free(reader.reader); // The deletion operation should be idempotent - error = opendal_operator_delete_with_cancel(this->p, this->path.c_str(), nullptr); + error = opendal_operator_delete(this->p, this->path.c_str()); EXPECT_EQ(error, nullptr); opendal_bytes_free(&r.data); // The directory "tmpdir/" should exist and should be a directory - error = opendal_operator_create_dir_with_cancel(this->p, "tmpdir/", nullptr); + error = opendal_operator_create_dir(this->p, "tmpdir/"); EXPECT_EQ(error, nullptr); - auto stat = opendal_operator_stat_with_cancel(this->p, "tmpdir/", nullptr); + auto stat = opendal_operator_stat(this->p, "tmpdir/"); EXPECT_EQ(stat.error, nullptr); EXPECT_TRUE(opendal_metadata_is_dir(stat.meta)); EXPECT_FALSE(opendal_metadata_is_file(stat.meta)); opendal_metadata_free(stat.meta); - error = opendal_operator_delete_with_cancel(this->p, "tmpdir/", nullptr); + error = opendal_operator_delete(this->p, "tmpdir/"); EXPECT_EQ(error, nullptr); } diff --git a/bindings/c/tests/error_msg.cpp b/bindings/c/tests/error_msg.cpp index 4813dda28499..16bc57aaed96 100644 --- a/bindings/c/tests/error_msg.cpp +++ b/bindings/c/tests/error_msg.cpp @@ -49,7 +49,7 @@ TEST_F(OpendalErrorTest, ErrorReadTest) { // Initialize a operator for "memory" backend, with no options // The read is supposed to fail - opendal_result_read r = opendal_operator_read_with_cancel(this->p, "/testpath", nullptr); + opendal_result_read r = opendal_operator_read(this->p, "/testpath"); ASSERT_NE(r.error, nullptr); ASSERT_EQ(r.error->code, OPENDAL_NOT_FOUND); diff --git a/bindings/c/tests/example_test.cpp b/bindings/c/tests/example_test.cpp index b7c4108e2f89..bb945319470f 100644 --- a/bindings/c/tests/example_test.cpp +++ b/bindings/c/tests/example_test.cpp @@ -39,7 +39,7 @@ void simple_test() printf("Testing with service: %s\n", config->scheme); // Test basic operator functionality - opendal_error* error = opendal_operator_check_with_cancel(config->operator_instance, nullptr); + opendal_error* error = opendal_operator_check(config->operator_instance); if (error) { printf("Operator check failed: %d\n", error->code); if (error->message.data) { @@ -69,7 +69,7 @@ void simple_test() .len = strlen(test_content), .capacity = strlen(test_content) }; - error = opendal_operator_write_with_cancel(config->operator_instance, test_path, &data, nullptr); + error = opendal_operator_write(config->operator_instance, test_path, &data); if (error) { printf("Write failed: %d\n", error->code); opendal_error_free(error); @@ -77,7 +77,7 @@ void simple_test() printf("Write successful!\n"); // Read test data back - opendal_result_read result = opendal_operator_read_with_cancel(config->operator_instance, test_path, nullptr); + opendal_result_read result = opendal_operator_read(config->operator_instance, test_path); if (result.error) { printf("Read failed: %d\n", result.error->code); opendal_error_free(result.error); @@ -96,7 +96,7 @@ void simple_test() } // Cleanup - opendal_operator_delete_with_cancel(config->operator_instance, test_path, nullptr); + opendal_operator_delete(config->operator_instance, test_path); } } else { printf("Write/read not supported by this service\n"); diff --git a/bindings/c/tests/list.cpp b/bindings/c/tests/list.cpp index efc196d09622..391bc1e460d9 100644 --- a/bindings/c/tests/list.cpp +++ b/bindings/c/tests/list.cpp @@ -65,11 +65,11 @@ TEST_F(OpendalListTest, ListDirTest) }; // write must succeed - EXPECT_EQ(opendal_operator_write_with_cancel(this->p, path.c_str(), &data, nullptr), + EXPECT_EQ(opendal_operator_write(this->p, path.c_str(), &data), nullptr); // list must succeed since the write succeeded - opendal_result_list l = opendal_operator_list_with_cancel(this->p, (dname + "/").c_str(), nullptr); + opendal_result_list l = opendal_operator_list(this->p, (dname + "/").c_str()); EXPECT_EQ(l.error, nullptr); opendal_lister* lister = l.lister; @@ -77,14 +77,14 @@ TEST_F(OpendalListTest, ListDirTest) // start checking the lister's result bool found = false; - opendal_result_lister_next result = opendal_lister_next_with_cancel(lister, nullptr); + opendal_result_lister_next result = opendal_lister_next(lister); EXPECT_EQ(result.error, nullptr); opendal_entry* entry = result.entry; while (entry) { char* de_path = opendal_entry_path(entry); // stat must succeed - opendal_result_stat s = opendal_operator_stat_with_cancel(this->p, de_path, nullptr); + opendal_result_stat s = opendal_operator_stat(this->p, de_path); EXPECT_EQ(s.error, nullptr); if (!strcmp(de_path, path.c_str())) { @@ -99,7 +99,7 @@ TEST_F(OpendalListTest, ListDirTest) opendal_metadata_free(s.meta); opendal_entry_free(entry); - result = opendal_lister_next_with_cancel(lister, nullptr); + result = opendal_lister_next(lister); EXPECT_EQ(result.error, nullptr); entry = result.entry; } @@ -108,7 +108,7 @@ TEST_F(OpendalListTest, ListDirTest) EXPECT_TRUE(found); // delete - EXPECT_EQ(opendal_operator_delete_with_cancel(this->p, path.c_str(), nullptr), + EXPECT_EQ(opendal_operator_delete(this->p, path.c_str()), nullptr); opendal_lister_free(lister); diff --git a/bindings/c/tests/reader.cpp b/bindings/c/tests/reader.cpp index b5276f03edb4..2cbde7f9ba5a 100644 --- a/bindings/c/tests/reader.cpp +++ b/bindings/c/tests/reader.cpp @@ -54,41 +54,41 @@ TEST_F(OpendalReaderTest, SeekTest) }; // Prepare the file to be read immediately - opendal_error* err = opendal_operator_write_with_cancel(this->p, "/testseek", &data, nullptr); + opendal_error* err = opendal_operator_write(this->p, "/testseek", &data); EXPECT_EQ(err, nullptr); - opendal_result_operator_reader r = opendal_operator_reader_with_cancel(this->p, "/testseek", nullptr); + opendal_result_operator_reader r = opendal_operator_reader(this->p, "/testseek"); EXPECT_EQ(r.error, nullptr); // Test seek set - opendal_result_reader_seek seek_result = opendal_reader_seek_with_cancel(r.reader, 6, OPENDAL_SEEK_SET, nullptr); + opendal_result_reader_seek seek_result = opendal_reader_seek(r.reader, 6, OPENDAL_SEEK_SET); EXPECT_EQ(seek_result.pos, 6); EXPECT_EQ(seek_result.error, nullptr); char buf1[64] = { 0 }; - opendal_result_reader_read read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf1, 7, nullptr); + opendal_result_reader_read read_result = opendal_reader_read(r.reader, (uint8_t*)buf1, 7); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 7); EXPECT_EQ(std::string(buf1), "Gabcdef"); // Test seek cur, now we step on '3' - seek_result = opendal_reader_seek_with_cancel(r.reader, 3, OPENDAL_SEEK_CUR, nullptr); + seek_result = opendal_reader_seek(r.reader, 3, OPENDAL_SEEK_CUR); EXPECT_EQ(seek_result.pos, 16); EXPECT_EQ(seek_result.error, nullptr); char buf2[64] = { 0 }; - read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf2, 32 /* no more 32 bytes*/, nullptr); + read_result = opendal_reader_read(r.reader, (uint8_t*)buf2, 32 /* no more 32 bytes*/); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 5); EXPECT_EQ(std::string(buf2), "34567"); // Test seek end, now we step on 'g' - seek_result = opendal_reader_seek_with_cancel(r.reader, -8, OPENDAL_SEEK_END, nullptr); + seek_result = opendal_reader_seek(r.reader, -8, OPENDAL_SEEK_END); EXPECT_EQ(seek_result.pos, 13); EXPECT_EQ(seek_result.error, nullptr); char buf3[64] = { 0 }; - read_result = opendal_reader_read_with_cancel(r.reader, (uint8_t*)buf3, 32 /* no more 32 bytes*/, nullptr); + read_result = opendal_reader_read(r.reader, (uint8_t*)buf3, 32 /* no more 32 bytes*/); EXPECT_EQ(read_result.error, nullptr); EXPECT_EQ(read_result.size, 8); EXPECT_EQ(std::string(buf3), "g1234567"); diff --git a/bindings/c/tests/test_runner.cpp b/bindings/c/tests/test_runner.cpp index 9e6bf2779df9..cf1bf2397f3c 100644 --- a/bindings/c/tests/test_runner.cpp +++ b/bindings/c/tests/test_runner.cpp @@ -173,7 +173,7 @@ int main(int argc, char* argv[]) // Check operator availability - only perform list-based check if list is supported if (cap.list) { - opendal_error* check_error = opendal_operator_check_with_cancel(config->operator_instance, nullptr); + opendal_error* check_error = opendal_operator_check(config->operator_instance); if (check_error) { printf("Operator check failed: error code %d\n", check_error->code); if (check_error->message.data) { diff --git a/bindings/c/tests/test_suites_basic.cpp b/bindings/c/tests/test_suites_basic.cpp index 62a405c25a55..a64454b622d4 100644 --- a/bindings/c/tests/test_suites_basic.cpp +++ b/bindings/c/tests/test_suites_basic.cpp @@ -30,7 +30,7 @@ void test_check(opendal_test_context* ctx) if (cap.list) { // Only perform the standard check if list operations are supported - opendal_error* error = opendal_operator_check_with_cancel(ctx->config->operator_instance, nullptr); + opendal_error* error = opendal_operator_check(ctx->config->operator_instance); OPENDAL_ASSERT_NO_ERROR(error, "Check operation should succeed"); } else { // For KV adapters that don't support list, the operator creation itself is @@ -53,11 +53,11 @@ void test_write_read(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Read data back - opendal_result_read result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_read result = opendal_operator_read(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(result.error, "Read operation should succeed"); OPENDAL_ASSERT_EQ(strlen(content), result.data.len, "Read data length should match written data"); @@ -68,7 +68,7 @@ void test_write_read(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&result.data); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Exists operation @@ -78,7 +78,7 @@ void test_exists(opendal_test_context* ctx) const char* content = "test"; // File should not exist initially - opendal_result_exists result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_exists result = opendal_operator_exists(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(!result.exists, "File should not exist initially"); @@ -88,16 +88,16 @@ void test_exists(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // File should exist now - result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); + result = opendal_operator_exists(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(result.exists, "File should exist after write"); // Cleanup - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Stat operation @@ -112,11 +112,11 @@ void test_stat(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Stat file - opendal_result_stat result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_stat result = opendal_operator_stat(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(result.error, "Stat operation should succeed"); OPENDAL_ASSERT_NOT_NULL(result.meta, "Metadata should not be null"); @@ -131,7 +131,7 @@ void test_stat(opendal_test_context* ctx) // Cleanup opendal_metadata_free(result.meta); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Delete operation @@ -146,27 +146,27 @@ void test_delete(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Verify file exists - opendal_result_exists exists_result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_exists exists_result = opendal_operator_exists(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(exists_result.error, "Exists operation should succeed"); OPENDAL_ASSERT(exists_result.exists, "File should exist before deletion"); // Delete file - error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(error, "Delete operation should succeed"); // Verify file no longer exists - exists_result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); + exists_result = opendal_operator_exists(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(exists_result.error, "Exists operation should succeed"); OPENDAL_ASSERT(!exists_result.exists, "File should not exist after deletion"); // Delete should be idempotent - error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(error, "Delete operation should be idempotent"); } @@ -176,16 +176,16 @@ void test_create_dir(opendal_test_context* ctx) const char* dir_path = "test_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // Verify directory exists - opendal_result_exists result = opendal_operator_exists_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_exists result = opendal_operator_exists(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(result.error, "Exists operation should succeed"); OPENDAL_ASSERT(result.exists, "Directory should exist after creation"); // Stat directory - opendal_result_stat stat_result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_stat stat_result = opendal_operator_stat(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(stat_result.error, "Stat operation should succeed"); OPENDAL_ASSERT(opendal_metadata_is_dir(stat_result.meta), "Should be identified as directory"); @@ -194,7 +194,7 @@ void test_create_dir(opendal_test_context* ctx) // Cleanup opendal_metadata_free(stat_result.meta); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Define the basic test suite diff --git a/bindings/c/tests/test_suites_list.cpp b/bindings/c/tests/test_suites_list.cpp index 3f878a4b35fa..33aebe7ab052 100644 --- a/bindings/c/tests/test_suites_list.cpp +++ b/bindings/c/tests/test_suites_list.cpp @@ -28,7 +28,7 @@ void test_list_basic(opendal_test_context* ctx) const char* dir_path = "test_list_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // Create some test files @@ -42,20 +42,20 @@ void test_list_basic(opendal_test_context* ctx) data.data = (uint8_t*)"test content"; data.len = 12; data.capacity = 12; - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, - test_files[i], &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, + test_files[i], &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); } // List directory - opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); // Collect all entries std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -91,9 +91,9 @@ void test_list_basic(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); for (size_t i = 0; i < num_files; i++) { - opendal_operator_delete_with_cancel(ctx->config->operator_instance, test_files[i], nullptr); + opendal_operator_delete(ctx->config->operator_instance, test_files[i]); } - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Test: List empty directory @@ -102,18 +102,18 @@ void test_list_empty_dir(opendal_test_context* ctx) const char* dir_path = "test_empty_dir/"; // Create directory - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir operation should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); // Collect entries std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -142,7 +142,7 @@ void test_list_empty_dir(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Test: List nested directories @@ -154,10 +154,10 @@ void test_list_nested(opendal_test_context* ctx) const char* file_in_sub = "test_nested/subdir/sub_file.txt"; // Create directories - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, base_dir, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, base_dir); OPENDAL_ASSERT_NO_ERROR(error, "Create base dir should succeed"); - error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); + error = opendal_operator_create_dir(ctx->config->operator_instance, sub_dir); OPENDAL_ASSERT_NO_ERROR(error, "Create sub dir should succeed"); // Create files @@ -166,21 +166,21 @@ void test_list_nested(opendal_test_context* ctx) data.len = 12; data.capacity = 12; - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_in_base, - &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_in_base, + &data); OPENDAL_ASSERT_NO_ERROR(error, "Write to base dir should succeed"); - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_in_sub, - &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_in_sub, + &data); OPENDAL_ASSERT_NO_ERROR(error, "Write to sub dir should succeed"); // List base directory - opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, base_dir, nullptr); + opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, base_dir); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); std::set found_paths; while (true) { - opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -220,10 +220,10 @@ void test_list_nested(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_in_sub, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_in_base, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, base_dir, nullptr); + opendal_operator_delete(ctx->config->operator_instance, file_in_sub); + opendal_operator_delete(ctx->config->operator_instance, file_in_base); + opendal_operator_delete(ctx->config->operator_instance, sub_dir); + opendal_operator_delete(ctx->config->operator_instance, base_dir); } // Test: Entry name vs path @@ -233,23 +233,23 @@ void test_entry_name_path(opendal_test_context* ctx) const char* file_path = "test_entry_names/test_file.txt"; // Create directory and file - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)"test"; data.len = 4; data.capacity = 4; - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); bool found_file = false; while (true) { - opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -279,8 +279,8 @@ void test_entry_name_path(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, file_path); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Test: Entry metadata from lister @@ -292,24 +292,24 @@ void test_entry_metadata(opendal_test_context* ctx) const size_t content_len = strlen(content_str); // Create directory and file - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)content_str; data.len = content_len; data.capacity = content_len; - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // List directory - opendal_result_list list_result = opendal_operator_list_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_result_list list_result = opendal_operator_list(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(list_result.error, "List operation should succeed"); bool found_file = false; bool found_dir = false; while (true) { - opendal_result_lister_next next_result = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next_result = opendal_lister_next(list_result.lister); if (next_result.error) { OPENDAL_ASSERT_NO_ERROR(next_result.error, "Lister next should not fail"); break; @@ -344,8 +344,8 @@ void test_entry_metadata(opendal_test_context* ctx) // Cleanup opendal_lister_free(list_result.lister); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, file_path); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Test: list_with default options (null opts behaves like list) @@ -354,25 +354,25 @@ void test_list_with_default_options(opendal_test_context* ctx) const char* dir_path = "test_list_with_default/"; const char* file_path = "test_list_with_default/file.txt"; - opendal_error* error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_error* error = opendal_operator_create_dir(ctx->config->operator_instance, dir_path); OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed"); opendal_bytes data; data.data = (uint8_t*)"content"; data.len = 7; data.capacity = 7; - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_path, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed"); // Pass NULL opts — should behave identically to opendal_operator_list - opendal_result_list list_result = opendal_operator_list_with_options_cancel( - ctx->config->operator_instance, dir_path, NULL, nullptr); + opendal_result_list list_result = opendal_operator_list_with( + ctx->config->operator_instance, dir_path, NULL); OPENDAL_ASSERT_NO_ERROR(list_result.error, "list_with(NULL opts) should succeed"); OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null"); bool found_file = false; while (true) { - opendal_result_lister_next next = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next = opendal_lister_next(list_result.lister); if (next.error) { OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail"); break; @@ -390,8 +390,8 @@ void test_list_with_default_options(opendal_test_context* ctx) OPENDAL_ASSERT(found_file, "Should find the file with null opts"); opendal_lister_free(list_result.lister); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_path, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, dir_path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, file_path); + opendal_operator_delete(ctx->config->operator_instance, dir_path); } // Test: list_with recursive=true returns entries in nested directories @@ -410,17 +410,17 @@ void test_list_with_recursive(opendal_test_context* ctx) data.capacity = 1; opendal_error* error; - error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, base_dir, nullptr); + error = opendal_operator_create_dir(ctx->config->operator_instance, base_dir); OPENDAL_ASSERT_NO_ERROR(error, "Create base dir should succeed"); - error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); + error = opendal_operator_create_dir(ctx->config->operator_instance, sub_dir); OPENDAL_ASSERT_NO_ERROR(error, "Create sub dir should succeed"); - error = opendal_operator_create_dir_with_cancel(ctx->config->operator_instance, deep_dir, nullptr); + error = opendal_operator_create_dir(ctx->config->operator_instance, deep_dir); OPENDAL_ASSERT_NO_ERROR(error, "Create deep dir should succeed"); - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_top, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_top, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write top file should succeed"); - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_sub, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_sub, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write sub file should succeed"); - error = opendal_operator_write_with_cancel(ctx->config->operator_instance, file_deep, &data, nullptr); + error = opendal_operator_write(ctx->config->operator_instance, file_deep, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write deep file should succeed"); // List recursively from base_dir @@ -428,8 +428,8 @@ void test_list_with_recursive(opendal_test_context* ctx) OPENDAL_ASSERT_NOT_NULL(opts, "list_options_new should not return NULL"); opendal_list_options_set_recursive(opts, true); - opendal_result_list list_result = opendal_operator_list_with_options_cancel( - ctx->config->operator_instance, base_dir, opts, nullptr); + opendal_result_list list_result = opendal_operator_list_with( + ctx->config->operator_instance, base_dir, opts); opendal_list_options_free(opts); OPENDAL_ASSERT_NO_ERROR(list_result.error, "Recursive list should succeed"); @@ -437,7 +437,7 @@ void test_list_with_recursive(opendal_test_context* ctx) std::unordered_set found_paths; while (true) { - opendal_result_lister_next next = opendal_lister_next_with_cancel(list_result.lister, nullptr); + opendal_result_lister_next next = opendal_lister_next(list_result.lister); if (next.error) { OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail"); break; @@ -460,12 +460,12 @@ void test_list_with_recursive(opendal_test_context* ctx) "Recursive list must include file in deep directory"); // Cleanup - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_deep, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_sub, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, file_top, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, deep_dir, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, sub_dir, nullptr); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, base_dir, nullptr); + opendal_operator_delete(ctx->config->operator_instance, file_deep); + opendal_operator_delete(ctx->config->operator_instance, file_sub); + opendal_operator_delete(ctx->config->operator_instance, file_top); + opendal_operator_delete(ctx->config->operator_instance, deep_dir); + opendal_operator_delete(ctx->config->operator_instance, sub_dir); + opendal_operator_delete(ctx->config->operator_instance, base_dir); } // Define the list test suite diff --git a/bindings/c/tests/test_suites_presign.cpp b/bindings/c/tests/test_suites_presign.cpp index 616ee55a7932..4ade2815f877 100644 --- a/bindings/c/tests/test_suites_presign.cpp +++ b/bindings/c/tests/test_suites_presign.cpp @@ -391,10 +391,10 @@ void test_presign_read(opendal_test_context* ctx) data.len = strlen(content); data.capacity = strlen(content); - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_read_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); + opendal_result_presign presign_result = opendal_operator_presign_read(ctx->config->operator_instance, path, 3600); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign read should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -464,7 +464,7 @@ void test_presign_read(opendal_test_context* ctx) opendal_presigned_request_free(presign_result.req); - opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); OPENDAL_ASSERT(header_found, "Content-Length header should be present"); @@ -482,7 +482,7 @@ void test_presign_write(opendal_test_context* ctx) const char* content = "Presign write content"; size_t content_len = strlen(content); - opendal_result_presign presign_result = opendal_operator_presign_write_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); + opendal_result_presign presign_result = opendal_operator_presign_write(ctx->config->operator_instance, path, 3600); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign write should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -556,7 +556,7 @@ void test_presign_write(opendal_test_context* ctx) presign_cleanup_curl(curl, chunk); opendal_presigned_request_free(presign_result.req); - opendal_result_stat stat_res = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_stat stat_res = opendal_operator_stat(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(stat_res.error, "Stat after presign write should succeed"); OPENDAL_ASSERT_NOT_NULL(stat_res.meta, "Stat metadata should not be null"); OPENDAL_ASSERT_EQ(content_len, (size_t)opendal_metadata_content_length(stat_res.meta), @@ -564,14 +564,14 @@ void test_presign_write(opendal_test_context* ctx) opendal_metadata_free(stat_res.meta); - opendal_result_read read_res = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_read read_res = opendal_operator_read(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(read_res.error, "Read after presign write should succeed"); OPENDAL_ASSERT_EQ(content_len, read_res.data.len, "Read length should match uploaded content length"); OPENDAL_ASSERT(memcmp(content, read_res.data.data, read_res.data.len) == 0, "Read content should match uploaded content"); opendal_bytes_free(&read_res.data); - opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); } @@ -586,10 +586,10 @@ void test_presign_stat(opendal_test_context* ctx) data.data = (uint8_t*)content; data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_stat_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); + opendal_result_presign presign_result = opendal_operator_presign_stat(ctx->config->operator_instance, path, 3600); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign stat should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -652,7 +652,7 @@ void test_presign_stat(opendal_test_context* ctx) OPENDAL_ASSERT_EQ(content_len, header_ctx.content_length, "Stat Content-Length should match written data"); - opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed"); } @@ -674,10 +674,10 @@ void test_presign_delete(opendal_test_context* ctx) data.data = (uint8_t*)content; data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); - opendal_result_presign presign_result = opendal_operator_presign_delete_with_cancel(ctx->config->operator_instance, path, 3600, nullptr); + opendal_result_presign presign_result = opendal_operator_presign_delete(ctx->config->operator_instance, path, 3600); OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign delete should succeed"); OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not be null"); @@ -712,12 +712,12 @@ void test_presign_delete(opendal_test_context* ctx) presign_cleanup_curl(curl, chunk); opendal_presigned_request_free(presign_result.req); - opendal_result_exists exists_res = opendal_operator_exists_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_exists exists_res = opendal_operator_exists(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(exists_res.error, "Exists after presign delete should succeed"); OPENDAL_ASSERT(!exists_res.exists, "Object should not exist after presign delete"); // Ensure cleanup is idempotent - opendal_error* delete_error = opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_error* delete_error = opendal_operator_delete(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(delete_error, "Delete after presign delete should be idempotent"); } diff --git a/bindings/c/tests/test_suites_reader_writer.cpp b/bindings/c/tests/test_suites_reader_writer.cpp index 29652e093466..0163f2024eab 100644 --- a/bindings/c/tests/test_suites_reader_writer.cpp +++ b/bindings/c/tests/test_suites_reader_writer.cpp @@ -31,18 +31,18 @@ void test_reader_basic(opendal_test_context* ctx) .data = (uint8_t*)content, .len = content_len, .capacity = content_len }; - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); OPENDAL_ASSERT_NOT_NULL(reader_result.reader, "Reader should not be null"); // Read entire content uint8_t buffer[100]; - opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, sizeof(buffer), nullptr); + opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, sizeof(buffer)); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read operation should succeed"); OPENDAL_ASSERT_EQ(content_len, read_result.size, "Read size should match content length"); @@ -53,7 +53,7 @@ void test_reader_basic(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Reader seek operations @@ -68,49 +68,49 @@ void test_reader_seek(opendal_test_context* ctx) .data = (uint8_t*)content, .len = content_len, .capacity = content_len }; - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); // Test seek from current position - opendal_result_reader_seek seek_result = opendal_reader_seek_with_cancel(reader_result.reader, 5, OPENDAL_SEEK_CUR, nullptr); + opendal_result_reader_seek seek_result = opendal_reader_seek(reader_result.reader, 5, OPENDAL_SEEK_CUR); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from current should succeed"); OPENDAL_ASSERT_EQ(5, seek_result.pos, "Position should be 5"); // Read after seek uint8_t buffer[5]; - opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); + opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, 5); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read after seek should succeed"); OPENDAL_ASSERT_EQ(5, read_result.size, "Should read 5 bytes"); OPENDAL_ASSERT(memcmp("56789", buffer, 5) == 0, "Should read correct content after seek"); // Test seek from beginning - seek_result = opendal_reader_seek_with_cancel(reader_result.reader, 0, OPENDAL_SEEK_SET, nullptr); + seek_result = opendal_reader_seek(reader_result.reader, 0, OPENDAL_SEEK_SET); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from beginning should succeed"); OPENDAL_ASSERT_EQ(0, seek_result.pos, "Position should be 0"); // Read from beginning - read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); + read_result = opendal_reader_read(reader_result.reader, buffer, 5); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read from beginning should succeed"); OPENDAL_ASSERT(memcmp("01234", buffer, 5) == 0, "Should read correct content from beginning"); // Test seek from end - seek_result = opendal_reader_seek_with_cancel(reader_result.reader, -5, OPENDAL_SEEK_END, nullptr); + seek_result = opendal_reader_seek(reader_result.reader, -5, OPENDAL_SEEK_END); OPENDAL_ASSERT_NO_ERROR(seek_result.error, "Seek from end should succeed"); OPENDAL_ASSERT_EQ(content_len - 5, seek_result.pos, "Position should be content_len - 5"); // Read from near end - read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, 5, nullptr); + read_result = opendal_reader_read(reader_result.reader, buffer, 5); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read from near end should succeed"); OPENDAL_ASSERT(memcmp("FGHIJ", buffer, 5) == 0, @@ -118,7 +118,7 @@ void test_reader_seek(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Basic writer operations @@ -129,7 +129,7 @@ void test_writer_basic(opendal_test_context* ctx) const char* content2 = "OpenDAL Writer!"; // Create writer - opendal_result_operator_writer writer_result = opendal_operator_writer_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_operator_writer writer_result = opendal_operator_writer(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(writer_result.error, "Writer creation should succeed"); OPENDAL_ASSERT_NOT_NULL(writer_result.writer, "Writer should not be null"); @@ -140,7 +140,7 @@ void test_writer_basic(opendal_test_context* ctx) data1.len = strlen(content1); data1.capacity = strlen(content1); - opendal_result_writer_write write_result = opendal_writer_write_with_cancel(writer_result.writer, &data1, nullptr); + opendal_result_writer_write write_result = opendal_writer_write(writer_result.writer, &data1); OPENDAL_ASSERT_NO_ERROR(write_result.error, "First write should succeed"); OPENDAL_ASSERT_EQ(strlen(content1), write_result.size, "Write size should match content length"); @@ -151,18 +151,18 @@ void test_writer_basic(opendal_test_context* ctx) data2.len = strlen(content2); data2.capacity = strlen(content2); - write_result = opendal_writer_write_with_cancel(writer_result.writer, &data2, nullptr); + write_result = opendal_writer_write(writer_result.writer, &data2); // Check if this is a OneShotWriter limitation if (write_result.error != NULL && write_result.error->message.data != NULL && strstr((char*)write_result.error->message.data, "OneShotWriter doesn't support multiple write") != NULL) { printf("Note: Service uses OneShotWriter, skipping multiple write test\n"); // Close current writer and verify single write worked - opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); + opendal_error* error = opendal_writer_close(writer_result.writer); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify first write content - opendal_result_read read_result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_read read_result = opendal_operator_read(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); OPENDAL_ASSERT_EQ(strlen(content1), read_result.data.len, "Content length should match first write"); @@ -173,7 +173,7 @@ void test_writer_basic(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&read_result.data); opendal_writer_free(writer_result.writer); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); return; } @@ -182,11 +182,11 @@ void test_writer_basic(opendal_test_context* ctx) "Write size should match content length"); // Close writer - opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); + opendal_error* error = opendal_writer_close(writer_result.writer); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify written content - opendal_result_read read_result = opendal_operator_read_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_read read_result = opendal_operator_read(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); size_t expected_len = strlen(content1) + strlen(content2); @@ -205,7 +205,7 @@ void test_writer_basic(opendal_test_context* ctx) // Cleanup opendal_bytes_free(&read_result.data); opendal_writer_free(writer_result.writer); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Writer with large data @@ -216,7 +216,7 @@ void test_writer_large_data(opendal_test_context* ctx) const size_t num_chunks = 10; // Create writer - opendal_result_operator_writer writer_result = opendal_operator_writer_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_operator_writer writer_result = opendal_operator_writer(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(writer_result.error, "Writer creation should succeed"); @@ -233,7 +233,7 @@ void test_writer_large_data(opendal_test_context* ctx) bool is_one_shot_writer = false; for (size_t i = 0; i < num_chunks; i++) { - opendal_result_writer_write write_result = opendal_writer_write_with_cancel(writer_result.writer, &chunk, nullptr); + opendal_result_writer_write write_result = opendal_writer_write(writer_result.writer, &chunk); // Check for OneShotWriter limitation on subsequent writes if (i > 0 && write_result.error != NULL && write_result.error->message.data != NULL && strstr((char*)write_result.error->message.data, "OneShotWriter doesn't support multiple write") != NULL) { @@ -249,11 +249,11 @@ void test_writer_large_data(opendal_test_context* ctx) } // Close writer - opendal_error* error = opendal_writer_close_with_cancel(writer_result.writer, nullptr); + opendal_error* error = opendal_writer_close(writer_result.writer); OPENDAL_ASSERT_NO_ERROR(error, "Writer close should succeed"); // Verify total size - adjust expectations for OneShotWriter - opendal_result_stat stat_result = opendal_operator_stat_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_stat stat_result = opendal_operator_stat(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(stat_result.error, "Stat should succeed"); if (is_one_shot_writer) { @@ -272,7 +272,7 @@ void test_writer_large_data(opendal_test_context* ctx) free(chunk_data); opendal_metadata_free(stat_result.meta); opendal_writer_free(writer_result.writer); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Test: Reader partial read @@ -288,11 +288,11 @@ void test_reader_partial_read(opendal_test_context* ctx) data.len = content_len; data.capacity = content_len; - opendal_error* error = opendal_operator_write_with_cancel(ctx->config->operator_instance, path, &data, nullptr); + opendal_error* error = opendal_operator_write(ctx->config->operator_instance, path, &data); OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed"); // Create reader - opendal_result_operator_reader reader_result = opendal_operator_reader_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_result_operator_reader reader_result = opendal_operator_reader(ctx->config->operator_instance, path); OPENDAL_ASSERT_NO_ERROR(reader_result.error, "Reader creation should succeed"); @@ -302,7 +302,7 @@ void test_reader_partial_read(opendal_test_context* ctx) size_t total_read = 0; while (total_read < content_len) { - opendal_result_reader_read read_result = opendal_reader_read_with_cancel(reader_result.reader, buffer, chunk_size, nullptr); + opendal_result_reader_read read_result = opendal_reader_read(reader_result.reader, buffer, chunk_size); OPENDAL_ASSERT_NO_ERROR(read_result.error, "Read should succeed"); if (read_result.size == 0) { @@ -321,7 +321,7 @@ void test_reader_partial_read(opendal_test_context* ctx) // Cleanup opendal_reader_free(reader_result.reader); - opendal_operator_delete_with_cancel(ctx->config->operator_instance, path, nullptr); + opendal_operator_delete(ctx->config->operator_instance, path); } // Define the reader/writer test suite diff --git a/bindings/d/source/opendal/operator.d b/bindings/d/source/opendal/operator.d index a6f3330343b1..a3fe616fbf0c 100644 --- a/bindings/d/source/opendal/operator.d +++ b/bindings/d/source/opendal/operator.d @@ -48,7 +48,7 @@ struct Operator void write(string path, ubyte[] data) @trusted { opendal_bytes bytes = opendal_bytes(data.ptr, data.length, data.length); - auto error = opendal_operator_write_with_cancel(op, path.toStringz, &bytes, null); + auto error = opendal_operator_write(op, path.toStringz, &bytes); enforce(error is null, "Error writing data"); } @@ -75,7 +75,7 @@ struct Operator ubyte[] read(string path) @trusted { - auto result = opendal_operator_read_with_cancel(op, path.toStringz, null); + auto result = opendal_operator_read(op, path.toStringz); enforce(result.error is null, "Error reading data"); scope (exit) opendal_bytes_free(&result.data); @@ -84,33 +84,33 @@ struct Operator void remove(string path) @trusted { - auto error = opendal_operator_delete_with_cancel(op, path.toStringz, null); + auto error = opendal_operator_delete(op, path.toStringz); enforce(error is null, "Error deleting object"); } bool exists(string path) @trusted { - auto result = opendal_operator_exists_with_cancel(op, path.toStringz, null); + auto result = opendal_operator_exists(op, path.toStringz); enforce(result.error is null, "Error checking existence"); return result.exists; } Metadata stat(string path) @trusted { - auto result = opendal_operator_stat_with_cancel(op, path.toStringz, null); + auto result = opendal_operator_stat(op, path.toStringz); enforce(result.error is null, "Error getting metadata"); return Metadata(result.meta); } Entry[] list(string path) @trusted { - auto result = opendal_operator_list_with_cancel(op, path.toStringz, null); + auto result = opendal_operator_list(op, path.toStringz); enforce(result.error is null, "Error listing objects"); Entry[] entries; while (true) { - auto next = opendal_lister_next_with_cancel(result.lister, null); + auto next = opendal_lister_next(result.lister); if (next.entry is null) break; entries ~= Entry(next.entry); @@ -120,19 +120,19 @@ struct Operator void createDir(string path) @trusted { - auto error = opendal_operator_create_dir_with_cancel(op, path.toStringz, null); + auto error = opendal_operator_create_dir(op, path.toStringz); enforce(error is null, "Error creating directory"); } void rename(string src, string dest) @trusted { - auto error = opendal_operator_rename_with_cancel(op, src.toStringz, dest.toStringz, null); + auto error = opendal_operator_rename(op, src.toStringz, dest.toStringz); enforce(error is null, "Error renaming object"); } void copy(string src, string dest) @trusted { - auto error = opendal_operator_copy_with_cancel(op, src.toStringz, dest.toStringz, null); + auto error = opendal_operator_copy(op, src.toStringz, dest.toStringz); enforce(error is null, "Error copying object"); } diff --git a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift index ccd0dffbc2da..6e2bb3a7bd1c 100644 --- a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift +++ b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift @@ -62,7 +62,7 @@ public class Operator { let address = dataPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) let bytes = opendal_bytes(data: address, len: UInt(dataPointer.count), capacity: UInt(dataPointer.count)) return withUnsafePointer(to: bytes) { bytesPointer in - opendal_operator_write_with_cancel(nativeOp, path, bytesPointer, nil) + opendal_operator_write(nativeOp, path, bytesPointer) } } @@ -81,7 +81,7 @@ public class Operator { } public func blockingRead(_ path: String) throws -> Data { - var ret = opendal_operator_read_with_cancel(nativeOp, path, nil) + var ret = opendal_operator_read(nativeOp, path) if let err = ret.error { defer { opendal_error_free(err) diff --git a/bindings/zig/src/opendal.zig b/bindings/zig/src/opendal.zig index 0436cd3c5a8f..8060e57f3878 100644 --- a/bindings/zig/src/opendal.zig +++ b/bindings/zig/src/opendal.zig @@ -43,14 +43,14 @@ pub const Operator = struct { .len = data.len, .capacity = data.len, }; - if (c.opendal_operator_write_with_cancel(self.inner, path.ptr, &bytes, null)) |err| { + if (c.opendal_operator_write(self.inner, path.ptr, &bytes)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn read(self: *const Operator, path: []const u8) ![]const u8 { - const result = c.opendal_operator_read_with_cancel(self.inner, path.ptr, null); + const result = c.opendal_operator_read(self.inner, path.ptr); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -59,14 +59,14 @@ pub const Operator = struct { } pub fn delete(self: *const Operator, path: []const u8) !void { - if (c.opendal_operator_delete_with_cancel(self.inner, path.ptr, null)) |err| { + if (c.opendal_operator_delete(self.inner, path.ptr)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn stat(self: *const Operator, path: []const u8) !Metadata { - const result = c.opendal_operator_stat_with_cancel(self.inner, path.ptr, null); + const result = c.opendal_operator_stat(self.inner, path.ptr); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -75,7 +75,7 @@ pub const Operator = struct { } pub fn exists(self: *const Operator, path: []const u8) !bool { - const result = c.opendal_operator_exists_with_cancel(self.inner, path.ptr, null); + const result = c.opendal_operator_exists(self.inner, path.ptr); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -84,7 +84,7 @@ pub const Operator = struct { } pub fn list(self: *const Operator, path: []const u8) !Lister { - const result = c.opendal_operator_list_with_cancel(self.inner, path.ptr, null); + const result = c.opendal_operator_list(self.inner, path.ptr); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); @@ -93,21 +93,21 @@ pub const Operator = struct { } pub fn createDir(self: *const Operator, path: []const u8) !void { - if (c.opendal_operator_create_dir_with_cancel(self.inner, path.ptr, null)) |err| { + if (c.opendal_operator_create_dir(self.inner, path.ptr)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn rename(self: *const Operator, src: []const u8, dest: []const u8) !void { - if (c.opendal_operator_rename_with_cancel(self.inner, src.ptr, dest.ptr, null)) |err| { + if (c.opendal_operator_rename(self.inner, src.ptr, dest.ptr)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } } pub fn copy(self: *const Operator, src: []const u8, dest: []const u8) !void { - if (c.opendal_operator_copy_with_cancel(self.inner, src.ptr, dest.ptr, null)) |err| { + if (c.opendal_operator_copy(self.inner, src.ptr, dest.ptr)) |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); } @@ -162,7 +162,7 @@ pub const Lister = struct { } pub fn next(self: *const Lister) !?Entry { - const result = c.opendal_lister_next_with_cancel(self.inner, null); + const result = c.opendal_lister_next(self.inner); if (result.@"error") |err| { errdefer c.opendal_error_free(err); try codeToError(err.*.code); diff --git a/bindings/zig/test/bdd.zig b/bindings/zig/test/bdd.zig index 2046f1447f26..8d2f6ce79f6c 100644 --- a/bindings/zig/test/bdd.zig +++ b/bindings/zig/test/bdd.zig @@ -65,16 +65,16 @@ test "Opendal BDD test" { .len = dupe_content.len, .capacity = dupe_content.len, }; - const result = opendal.c.opendal_operator_write_with_cancel(testkit.p, testkit.path, &data, null); + const result = opendal.c.opendal_operator_write(testkit.p, testkit.path, &data); try testing.expectEqual(result, null); // The blocking file "test" should exist - const e: opendal.c.opendal_result_is_exist = opendal.c.opendal_operator_is_exist_with_cancel(testkit.p, testkit.path, null); + const e: opendal.c.opendal_result_is_exist = opendal.c.opendal_operator_is_exist(testkit.p, testkit.path); try testing.expectEqual(e.@"error", null); try testing.expect(e.is_exist); // The blocking file "test" entry mode must be file - const s: opendal.c.opendal_result_stat = opendal.c.opendal_operator_stat_with_cancel(testkit.p, testkit.path, null); + const s: opendal.c.opendal_result_stat = opendal.c.opendal_operator_stat(testkit.p, testkit.path); try testing.expectEqual(s.@"error", null); const meta: [*c]opendal.c.opendal_metadata = s.meta; try testing.expect(opendal.c.opendal_metadata_is_file(meta)); @@ -84,7 +84,7 @@ test "Opendal BDD test" { defer opendal.c.opendal_metadata_free(meta); // The blocking file "test" must have content "Hello, World!" - var r: opendal.c.opendal_result_read = opendal.c.opendal_operator_read_with_cancel(testkit.p, testkit.path, null); + var r: opendal.c.opendal_result_read = opendal.c.opendal_operator_read(testkit.p, testkit.path); defer opendal.c.opendal_bytes_free(&r.data); try testing.expect(r.@"error" == null); try testing.expectEqual(std.mem.len(testkit.content), r.data.len); From 9205b9e2a014a260967c953658ca0583e4b89dcf Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 07:46:28 +0000 Subject: [PATCH 05/13] fix double free --- bindings/go/context_test.go | 17 +++++++++++++++++ bindings/go/writer.go | 16 +++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index bbfa53b74bb9..080b87916b97 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -22,6 +22,7 @@ package opendal import ( "context" "errors" + "sync" "testing" "time" ) @@ -164,6 +165,22 @@ func TestWriterFreeIsIdempotent(t *testing.T) { w.free() } +func TestWriterCloseReleaseRunsOnce(t *testing.T) { + var count int + var releaseOnce sync.Once + release := func() { + releaseOnce.Do(func() { + count++ + }) + } + + release() + release() + if count != 1 { + t.Fatalf("release count = %d, want 1", count) + } +} + func TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { inner := &opendalWriter{} w := &Writer{inner: inner} diff --git a/bindings/go/writer.go b/bindings/go/writer.go index a35cf2653b61..d6a981602a0a 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -467,11 +467,21 @@ func (w *Writer) CloseWithContext(ctx context.Context) error { w.inner = nil w.mu.Unlock() + var releaseOnce sync.Once + release := func() { + releaseOnce.Do(func() { + ffiWriterFree.symbol(w.ctx)(inner) + }) + } + _, err := runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (struct{}, error) { - closeErr := ffiWriterCloseWithCancel.symbol(w.ctx)(inner, token) - ffiWriterFree.symbol(w.ctx)(inner) - return struct{}{}, closeErr + return struct{}{}, ffiWriterCloseWithCancel.symbol(w.ctx)(inner, token) + }, func(struct{}) { + release() }) + if err == nil { + release() + } return err } From dffadeca2ed753f0896e7eb97a774a8f88b191fe Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 07:51:32 +0000 Subject: [PATCH 06/13] fix double free if error --- bindings/go/context_test.go | 21 +++++++++++++++++++++ bindings/go/writer.go | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index 080b87916b97..ecb2e752e6b3 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -165,6 +165,27 @@ func TestWriterFreeIsIdempotent(t *testing.T) { 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 TestWriterCloseReleaseRunsOnce(t *testing.T) { var count int var releaseOnce sync.Once diff --git a/bindings/go/writer.go b/bindings/go/writer.go index d6a981602a0a..b447fbb35425 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -479,12 +479,16 @@ func (w *Writer) CloseWithContext(ctx context.Context) error { }, func(struct{}) { release() }) - if err == nil { + if shouldReleaseWriterAfterClose(err) { release() } return err } +func shouldReleaseWriterAfterClose(err error) bool { + return !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) +} + var _ io.WriteCloser = (*Writer)(nil) var ffiOperatorWriteWithCancel = newFFI(ffiOpts{ From 2e1822c8cf89548678e106ab8fc51e522e0b43e7 Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 18:40:57 +0000 Subject: [PATCH 07/13] recover comment --- bindings/c/include/opendal.h | 521 +++++++++++++------- bindings/c/src/lister.rs | 4 +- bindings/c/src/operator.rs | 919 ++++++++++++++++++++--------------- bindings/c/src/presign.rs | 20 +- bindings/c/src/reader.rs | 8 +- bindings/c/src/writer.rs | 8 +- 6 files changed, 906 insertions(+), 574 deletions(-) diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 7ad54d0de970..e2bf59828ac1 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -1089,7 +1089,9 @@ void opendal_cancel_token_cancel(const struct opendal_cancel_token *ptr); void opendal_cancel_token_free(struct opendal_cancel_token *ptr); /** - * \brief Return the next object with cancellation support. + * \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); @@ -1329,6 +1331,186 @@ 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_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`. * @@ -1374,22 +1556,18 @@ struct opendal_result_operator_new opendal_operator_new_with_layers(const char * * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Write raw bytes to `path` with cancellation support. */ -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); +struct opendal_error *opendal_operator_write(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes); /** - * \brief Write raw bytes to `path` with options and cancellation support. + * \brief Blocking write raw bytes to `path` with options. */ -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); +struct opendal_error *opendal_operator_write_with(const struct opendal_operator *op, + const char *path, + const struct opendal_bytes *bytes, + const struct opendal_write_options *opts); /** * \brief Blocking read the data from `path`. @@ -1431,20 +1609,44 @@ struct opendal_error *opendal_operator_write_with_options_cancel(const struct op * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Read data from `path` with cancellation support. */ -struct opendal_result_read opendal_operator_read_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_read opendal_operator_read(const struct opendal_operator *op, + const char *path); /** - * \brief Read data from `path` with options and cancellation support. + * \brief Blocking read the data from `path` with options. + * + * Read the data out from `path` blocking by operator, using the provided + * `opendal_read_options` to control the behavior, e.g. range, version, or + * conditional headers. + * + * @param op The opendal_operator created previously + * @param path The path you want to read the data out + * @param opts The options for the read operation; pass NULL to use defaults + * @see opendal_operator + * @see opendal_result_read + * @see opendal_read_options + * @see opendal_error + * @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated + * opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then + * the operation failed and the `data` field is a nullptr. + * + * \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. + * After your usage of that, please call opendal_bytes_free() to free the space. + * + * # Safety + * + * It is **safe** under the cases below + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -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); +struct opendal_result_read opendal_operator_read_with(const struct opendal_operator *op, + const char *path, + const struct opendal_read_options *opts); /** * \brief Blocking read the data from `path`. @@ -1483,12 +1685,9 @@ struct opendal_result_read opendal_operator_read_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Create a reader with cancellation support. */ -struct opendal_result_operator_reader opendal_operator_reader_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_operator_reader opendal_operator_reader(const struct opendal_operator *op, + const char *path); /** * \brief Blocking create a writer for the specified path. @@ -1527,20 +1726,16 @@ struct opendal_result_operator_reader opendal_operator_reader_with_cancel(const * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Create a writer with cancellation support. */ -struct opendal_result_operator_writer opendal_operator_writer_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_operator_writer opendal_operator_writer(const struct opendal_operator *op, + const char *path); /** - * \brief Create a writer with options and cancellation support. + * \brief Blocking create a writer for the specified path with options. */ -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); +struct opendal_result_operator_writer opendal_operator_writer_with(const struct opendal_operator *op, + const char *path, + const struct opendal_write_options *opts); /** * \brief Blocking delete the object in `path`. @@ -1583,20 +1778,34 @@ struct opendal_result_operator_writer opendal_operator_writer_with_options_cance * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Delete the object in `path` with cancellation support. */ -struct opendal_error *opendal_operator_delete_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_error *opendal_operator_delete(const struct opendal_operator *op, const char *path); /** - * \brief Delete the object in `path` with options and cancellation support. + * \brief Blocking delete the object in `path` with options. + * + * Delete the object in `path` blocking by `op`, using the provided `opendal_delete_options`. + * This is similar to `opendal_operator_delete` but allows specifying a version or + * requesting a recursive delete. + * + * @param op The opendal_operator created previously + * @param path The designated path you want to delete + * @param opts The options for the delete operation; pass NULL to use defaults + * @see opendal_delete_options + * @return NULL if succeeds, otherwise it contains the error code and error message. + * + * # Safety + * + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -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); +struct opendal_error *opendal_operator_delete_with(const struct opendal_operator *op, + const char *path, + const struct opendal_delete_options *opts); /** * \brief Check whether the path exists. @@ -1636,12 +1845,9 @@ struct opendal_error *opendal_operator_delete_with_options_cancel(const struct o * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Check whether the path exists with cancellation support. */ -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); +struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_operator *op, + const char *path); /** * \brief Check whether the path exists. @@ -1681,12 +1887,9 @@ struct opendal_result_is_exist opendal_operator_is_exist_with_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Check whether the path exists with cancellation support. */ -struct opendal_result_exists opendal_operator_exists_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_exists opendal_operator_exists(const struct opendal_operator *op, + const char *path); /** * \brief Stat the path, return its metadata. @@ -1725,20 +1928,37 @@ struct opendal_result_exists opendal_operator_exists_with_cancel(const struct op * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Stat the path with cancellation support. */ -struct opendal_result_stat opendal_operator_stat_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_stat opendal_operator_stat(const struct opendal_operator *op, + const char *path); /** - * \brief Stat the path with options and cancellation support. + * \brief Blocking stat the object in `path` with options. + * + * Stat the object in `path` with the provided `opendal_stat_options`. This is + * similar to `opendal_operator_stat` but allows passing options such as + * `version`, `if_match`, `if_none_match`, or response header overrides. + * + * @param op The opendal_operator created previously + * @param path The path you want to stat + * @param opts The options for the stat operation; pass NULL to use defaults + * @see opendal_operator + * @see opendal_result_stat + * @see opendal_stat_options + * @return Returns opendal_result_stat, containing a metadata and an opendal_error. + * + * # Safety + * + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -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); +struct opendal_result_stat opendal_operator_stat_with(const struct opendal_operator *op, + const char *path, + const struct opendal_stat_options *opts); /** * \brief Blocking list the objects in `path`. @@ -1789,20 +2009,36 @@ struct opendal_result_stat opendal_operator_stat_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief List the objects in `path` with cancellation support. */ -struct opendal_result_list opendal_operator_list_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_result_list opendal_operator_list(const struct opendal_operator *op, + const char *path); /** - * \brief List the objects in `path` with options and cancellation support. + * \brief Blocking list the objects in `path` with options. + * + * List the objects in `path` with the provided `opendal_list_options`. This is + * similar to `opendal_operator_list` but allows passing options such as + * `recursive` to control the listing behavior. + * + * @param op The opendal_operator created previously + * @param path The designated path you want to list + * @param opts The options for the list operation; pass NULL to use defaults + * @see opendal_lister + * @see opendal_list_options + * @return Returns opendal_result_list, containing a lister and an opendal_error. + * + * # Safety + * + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -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); +struct opendal_result_list opendal_operator_list_with(const struct opendal_operator *op, + const char *path, + const struct opendal_list_options *opts); /** * \brief Blocking create the directory in `path`. @@ -1838,12 +2074,9 @@ struct opendal_result_list opendal_operator_list_with_options_cancel(const struc * # Panic * * * If the `path` points to NULL, this function panics, i.e. exits with information - * - * \brief Create the directory in `path` with cancellation support. */ -struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opendal_operator *op, - const char *path, - const struct opendal_cancel_token *token); +struct opendal_error *opendal_operator_create_dir(const struct opendal_operator *op, + const char *path); /** * \brief Blocking rename the object in `path`. @@ -1887,13 +2120,10 @@ struct opendal_error *opendal_operator_create_dir_with_cancel(const struct opend * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information - * - * \brief Rename the object with cancellation support. */ -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); +struct opendal_error *opendal_operator_rename(const struct opendal_operator *op, + const char *src, + const char *dest); /** * \brief Blocking copy the object in `path`. @@ -1937,76 +2167,7 @@ struct opendal_error *opendal_operator_rename_with_cancel(const struct opendal_o * # Panic * * * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information - * - * \brief Copy the object with cancellation support. */ -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); - -struct opendal_error *opendal_operator_check_with_cancel(const struct opendal_operator *op, - const struct opendal_cancel_token *token); - -struct opendal_error *opendal_operator_write(const struct opendal_operator *op, - const char *path, - const struct opendal_bytes *bytes); - -struct opendal_error *opendal_operator_write_with(const struct opendal_operator *op, - const char *path, - const struct opendal_bytes *bytes, - const struct opendal_write_options *opts); - -struct opendal_result_read opendal_operator_read(const struct opendal_operator *op, - const char *path); - -struct opendal_result_read opendal_operator_read_with(const struct opendal_operator *op, - const char *path, - const struct opendal_read_options *opts); - -struct opendal_result_operator_reader opendal_operator_reader(const struct opendal_operator *op, - const char *path); - -struct opendal_result_operator_writer opendal_operator_writer(const struct opendal_operator *op, - const char *path); - -struct opendal_result_operator_writer opendal_operator_writer_with(const struct opendal_operator *op, - const char *path, - const struct opendal_write_options *opts); - -struct opendal_error *opendal_operator_delete(const struct opendal_operator *op, const char *path); - -struct opendal_error *opendal_operator_delete_with(const struct opendal_operator *op, - const char *path, - const struct opendal_delete_options *opts); - -struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_operator *op, - const char *path); - -struct opendal_result_exists opendal_operator_exists(const struct opendal_operator *op, - const char *path); - -struct opendal_result_stat opendal_operator_stat(const struct opendal_operator *op, - const char *path); - -struct opendal_result_stat opendal_operator_stat_with(const struct opendal_operator *op, - const char *path, - const struct opendal_stat_options *opts); - -struct opendal_result_list opendal_operator_list(const struct opendal_operator *op, - const char *path); - -struct opendal_result_list opendal_operator_list_with(const struct opendal_operator *op, - const char *path, - const struct opendal_list_options *opts); - -struct opendal_error *opendal_operator_create_dir(const struct opendal_operator *op, - const char *path); - -struct opendal_error *opendal_operator_rename(const struct opendal_operator *op, - const char *src, - const char *dest); - struct opendal_error *opendal_operator_copy(const struct opendal_operator *op, const char *src, const char *dest); @@ -2071,7 +2232,9 @@ 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 Presign a read operation with cancellation support. + * \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, @@ -2079,7 +2242,9 @@ struct opendal_result_presign opendal_operator_presign_read_with_cancel(const st const struct opendal_cancel_token *token); /** - * \brief Presign a write operation with cancellation support. + * \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, @@ -2087,7 +2252,9 @@ struct opendal_result_presign opendal_operator_presign_write_with_cancel(const s const struct opendal_cancel_token *token); /** - * \brief Presign a delete operation with cancellation support. + * \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, @@ -2095,25 +2262,39 @@ struct opendal_result_presign opendal_operator_presign_delete_with_cancel(const const struct opendal_cancel_token *token); /** - * \brief Presign a stat operation with cancellation support. + * \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. + */ struct opendal_result_presign opendal_operator_presign_read(const struct opendal_operator *op, const char *path, uint64_t expire_secs); +/** + * \brief Presign a write operation. + */ struct opendal_result_presign opendal_operator_presign_write(const struct opendal_operator *op, const char *path, uint64_t expire_secs); +/** + * \brief Presign a delete operation. + */ struct opendal_result_presign opendal_operator_presign_delete(const struct opendal_operator *op, const char *path, uint64_t expire_secs); +/** + * \brief Presign a stat operation. + */ struct opendal_result_presign opendal_operator_presign_stat(const struct opendal_operator *op, const char *path, uint64_t expire_secs); @@ -2525,7 +2706,9 @@ struct opendal_metadata *opendal_entry_metadata(const struct opendal_entry *self void opendal_entry_free(struct opendal_entry *ptr); /** - * \brief Read data from the reader with cancellation support. + * \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, @@ -2540,7 +2723,9 @@ struct opendal_result_reader_read opendal_reader_read(struct opendal_reader *sel uintptr_t len); /** - * \brief Seek to an offset with cancellation support. + * \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, @@ -2560,7 +2745,9 @@ struct opendal_result_reader_seek opendal_reader_seek(struct opendal_reader *sel void opendal_reader_free(struct opendal_reader *ptr); /** - * \brief Write data to the writer with cancellation support. + * \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, @@ -2573,7 +2760,9 @@ struct opendal_result_writer_write opendal_writer_write(struct opendal_writer *s const struct opendal_bytes *bytes); /** - * \brief Close the writer with cancellation support. + * \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); diff --git a/bindings/c/src/lister.rs b/bindings/c/src/lister.rs index 0a8334033362..525dab38eae2 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -77,7 +77,9 @@ impl opendal_lister { } } - /// \brief Return the next object with cancellation support. + /// \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, diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index 8767ff4059bd..8d4367a06124 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -302,6 +302,398 @@ 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(block_on_cancelable(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(block_on_cancelable(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(block_on_cancelable( + 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(block_on_cancelable(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 block_on_cancelable(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 block_on_cancelable(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 block_on_cancelable(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(block_on_cancelable( + 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(block_on_cancelable(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 block_on_cancelable(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 block_on_cancelable(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(block_on_cancelable( + 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(block_on_cancelable(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 block_on_cancelable(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 block_on_cancelable(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(block_on_cancelable(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(block_on_cancelable(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 { + 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(block_on_cancelable(token, async move { + op.copy(&src, &dest).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(block_on_cancelable(token, async move { op.check().await })) +} + /// \brief Blocking write raw bytes to `path`. /// /// Write the `bytes` into the `path` blocking by `op_ptr`. @@ -346,43 +738,24 @@ pub unsafe extern "C" fn opendal_operator_new_with_layers( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Write raw bytes to `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_write_with_cancel( +pub unsafe extern "C" fn opendal_operator_write( 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(block_on_cancelable(token, async move { - op.write(&path, bytes).await.map(|_| ()) - })) + unsafe { opendal_operator_write_with_cancel(op, path, bytes, std::ptr::null()) } } -/// \brief Write raw bytes to `path` with options and cancellation support. +/// \brief Blocking write raw bytes to `path` with options. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_write_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_write_with( 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(block_on_cancelable(token, async move { - op.write_options(&path, bytes, opts).await.map(|_| ()) - })) + unsafe { opendal_operator_write_with_options_cancel(op, path, bytes, opts, std::ptr::null()) } } /// \brief Blocking read the data from `path`. @@ -424,40 +797,50 @@ pub unsafe extern "C" fn opendal_operator_write_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Read data from `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_read_with_cancel( +pub unsafe extern "C" fn opendal_operator_read( 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(block_on_cancelable( - token, - async move { op.read(&path).await }, - )) + unsafe { opendal_operator_read_with_cancel(op, path, std::ptr::null()) } } -/// \brief Read data from `path` with options and cancellation support. +/// \brief Blocking read the data from `path` with options. +/// +/// Read the data out from `path` blocking by operator, using the provided +/// `opendal_read_options` to control the behavior, e.g. range, version, or +/// conditional headers. +/// +/// @param op The opendal_operator created previously +/// @param path The path you want to read the data out +/// @param opts The options for the read operation; pass NULL to use defaults +/// @see opendal_operator +/// @see opendal_result_read +/// @see opendal_read_options +/// @see opendal_error +/// @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated +/// opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then +/// the operation failed and the `data` field is a nullptr. +/// +/// \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. +/// After your usage of that, please call opendal_bytes_free() to free the space. +/// +/// # Safety +/// +/// It is **safe** under the cases below +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// +/// # Panic +/// +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] -pub unsafe extern "C" fn opendal_operator_read_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_read_with( 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(block_on_cancelable(token, async move { - op.read_options(&path, opts).await - })) + unsafe { opendal_operator_read_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking read the data from `path`. @@ -496,29 +879,12 @@ pub unsafe extern "C" fn opendal_operator_read_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Create a reader with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_reader_with_cancel( +pub unsafe extern "C" fn opendal_operator_reader( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_reader_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking create a writer for the specified path. @@ -557,53 +923,22 @@ pub unsafe extern "C" fn opendal_operator_reader_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Create a writer with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer_with_cancel( +pub unsafe extern "C" fn opendal_operator_writer( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_writer_with_cancel(op, path, std::ptr::null()) } } -/// \brief Create a writer with options and cancellation support. +/// \brief Blocking create a writer for the specified path with options. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_writer_with( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_writer_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking delete the object in `path`. @@ -646,36 +981,41 @@ pub unsafe extern "C" fn opendal_operator_writer_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Delete the object in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete_with_cancel( +pub unsafe extern "C" fn opendal_operator_delete( 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(block_on_cancelable( - token, - async move { op.delete(&path).await }, - )) + unsafe { opendal_operator_delete_with_cancel(op, path, std::ptr::null()) } } -/// \brief Delete the object in `path` with options and cancellation support. +/// \brief Blocking delete the object in `path` with options. +/// +/// Delete the object in `path` blocking by `op`, using the provided `opendal_delete_options`. +/// This is similar to `opendal_operator_delete` but allows specifying a version or +/// requesting a recursive delete. +/// +/// @param op The opendal_operator created previously +/// @param path The designated path you want to delete +/// @param opts The options for the delete operation; pass NULL to use defaults +/// @see opendal_delete_options +/// @return NULL if succeeds, otherwise it contains the error code and error message. +/// +/// # Safety +/// +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// +/// # Panic +/// +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_delete_with( 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(block_on_cancelable(token, async move { - op.delete_options(&path, opts).await - })) + unsafe { opendal_operator_delete_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Check whether the path exists. @@ -715,27 +1055,13 @@ pub unsafe extern "C" fn opendal_operator_delete_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Check whether the path exists with cancellation support. #[no_mangle] #[cfg_attr(cbindgen, cbindgen::ignore)] -pub unsafe extern "C" fn opendal_operator_is_exist_with_cancel( +pub unsafe extern "C" fn opendal_operator_is_exist( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_is_exist_with_cancel(op, path, std::ptr::null()) } } /// \brief Check whether the path exists. @@ -775,26 +1101,12 @@ pub unsafe extern "C" fn opendal_operator_is_exist_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Check whether the path exists with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_exists_with_cancel( +pub unsafe extern "C" fn opendal_operator_exists( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_exists_with_cancel(op, path, std::ptr::null()) } } /// \brief Stat the path, return its metadata. @@ -833,40 +1145,43 @@ pub unsafe extern "C" fn opendal_operator_exists_with_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Stat the path with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat_with_cancel( +pub unsafe extern "C" fn opendal_operator_stat( 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(block_on_cancelable( - token, - async move { op.stat(&path).await }, - )) + unsafe { opendal_operator_stat_with_cancel(op, path, std::ptr::null()) } } -/// \brief Stat the path with options and cancellation support. +/// \brief Blocking stat the object in `path` with options. +/// +/// Stat the object in `path` with the provided `opendal_stat_options`. This is +/// similar to `opendal_operator_stat` but allows passing options such as +/// `version`, `if_match`, `if_none_match`, or response header overrides. +/// +/// @param op The opendal_operator created previously +/// @param path The path you want to stat +/// @param opts The options for the stat operation; pass NULL to use defaults +/// @see opendal_operator +/// @see opendal_result_stat +/// @see opendal_stat_options +/// @return Returns opendal_result_stat, containing a metadata and an opendal_error. +/// +/// # Safety +/// +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// +/// # Panic +/// +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_stat_with( 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(block_on_cancelable(token, async move { - op.stat_options(&path, opts).await - })) + unsafe { opendal_operator_stat_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking list the objects in `path`. @@ -917,49 +1232,42 @@ pub unsafe extern "C" fn opendal_operator_stat_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief List the objects in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_list_with_cancel( +pub unsafe extern "C" fn opendal_operator_list( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_list_with_cancel(op, path, std::ptr::null()) } } -/// \brief List the objects in `path` with options and cancellation support. +/// \brief Blocking list the objects in `path` with options. +/// +/// List the objects in `path` with the provided `opendal_list_options`. This is +/// similar to `opendal_operator_list` but allows passing options such as +/// `recursive` to control the listing behavior. +/// +/// @param op The opendal_operator created previously +/// @param path The designated path you want to list +/// @param opts The options for the list operation; pass NULL to use defaults +/// @see opendal_lister +/// @see opendal_list_options +/// @return Returns opendal_result_list, containing a lister and an opendal_error. +/// +/// # Safety +/// +/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// the string. +/// +/// # Panic +/// +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] -pub unsafe extern "C" fn opendal_operator_list_with_options_cancel( +pub unsafe extern "C" fn opendal_operator_list_with( 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 block_on_cancelable(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), - }, - } + unsafe { opendal_operator_list_with_options_cancel(op, path, opts, std::ptr::null()) } } /// \brief Blocking create the directory in `path`. @@ -995,19 +1303,12 @@ pub unsafe extern "C" fn opendal_operator_list_with_options_cancel( /// # Panic /// /// * If the `path` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Create the directory in `path` with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( +pub unsafe extern "C" fn opendal_operator_create_dir( 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(block_on_cancelable(token, async move { - op.create_dir(&path).await - })) + unsafe { opendal_operator_create_dir_with_cancel(op, path, std::ptr::null()) } } /// \brief Blocking rename the object in `path`. @@ -1051,21 +1352,13 @@ pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Rename the object with cancellation support. #[no_mangle] -pub unsafe extern "C" fn opendal_operator_rename_with_cancel( +pub unsafe extern "C" fn opendal_operator_rename( 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(block_on_cancelable(token, async move { - op.rename(&src, &dest).await - })) + unsafe { opendal_operator_rename_with_cancel(op, src, dest, std::ptr::null()) } } /// \brief Blocking copy the object in `path`. @@ -1109,178 +1402,6 @@ pub unsafe extern "C" fn opendal_operator_rename_with_cancel( /// # Panic /// /// * If the `src` or `dest` points to NULL, this function panics, i.e. exits with information -/// -/// \brief Copy the object with cancellation support. -#[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 { - 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(block_on_cancelable(token, async move { - op.copy(&src, &dest).await.map(|_| ()) - })) -} - -#[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(block_on_cancelable(token, async move { op.check().await })) -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_write( - op: &opendal_operator, - path: *const c_char, - bytes: &opendal_bytes, -) -> *mut opendal_error { - unsafe { opendal_operator_write_with_cancel(op, path, bytes, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_write_with( - op: &opendal_operator, - path: *const c_char, - bytes: &opendal_bytes, - opts: *const opendal_write_options, -) -> *mut opendal_error { - unsafe { opendal_operator_write_with_options_cancel(op, path, bytes, opts, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_read( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_read { - unsafe { opendal_operator_read_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_read_with( - op: &opendal_operator, - path: *const c_char, - opts: *const opendal_read_options, -) -> opendal_result_read { - unsafe { opendal_operator_read_with_options_cancel(op, path, opts, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_reader( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_operator_reader { - unsafe { opendal_operator_reader_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_operator_writer { - unsafe { opendal_operator_writer_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_writer_with( - op: &opendal_operator, - path: *const c_char, - opts: *const opendal_write_options, -) -> opendal_result_operator_writer { - unsafe { opendal_operator_writer_with_options_cancel(op, path, opts, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete( - op: &opendal_operator, - path: *const c_char, -) -> *mut opendal_error { - unsafe { opendal_operator_delete_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_delete_with( - op: &opendal_operator, - path: *const c_char, - opts: *const opendal_delete_options, -) -> *mut opendal_error { - unsafe { opendal_operator_delete_with_options_cancel(op, path, opts, std::ptr::null()) } -} - -#[no_mangle] -#[cfg_attr(cbindgen, cbindgen::ignore)] -pub unsafe extern "C" fn opendal_operator_is_exist( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_is_exist { - unsafe { opendal_operator_is_exist_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_exists( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_exists { - unsafe { opendal_operator_exists_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_stat { - unsafe { opendal_operator_stat_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_stat_with( - op: &opendal_operator, - path: *const c_char, - opts: *const opendal_stat_options, -) -> opendal_result_stat { - unsafe { opendal_operator_stat_with_options_cancel(op, path, opts, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_list( - op: &opendal_operator, - path: *const c_char, -) -> opendal_result_list { - unsafe { opendal_operator_list_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_list_with( - op: &opendal_operator, - path: *const c_char, - opts: *const opendal_list_options, -) -> opendal_result_list { - unsafe { opendal_operator_list_with_options_cancel(op, path, opts, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_create_dir( - op: &opendal_operator, - path: *const c_char, -) -> *mut opendal_error { - unsafe { opendal_operator_create_dir_with_cancel(op, path, std::ptr::null()) } -} - -#[no_mangle] -pub unsafe extern "C" fn opendal_operator_rename( - op: &opendal_operator, - src: *const c_char, - dest: *const c_char, -) -> *mut opendal_error { - unsafe { opendal_operator_rename_with_cancel(op, src, dest, std::ptr::null()) } -} - #[no_mangle] pub unsafe extern "C" fn opendal_operator_copy( op: &opendal_operator, diff --git a/bindings/c/src/presign.rs b/bindings/c/src/presign.rs index 4c1afcb2db59..065e1059b152 100644 --- a/bindings/c/src/presign.rs +++ b/bindings/c/src/presign.rs @@ -134,7 +134,9 @@ pub struct opendal_result_presign { pub error: *mut opendal_error, } -/// \brief Presign a read operation with cancellation support. +/// \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_with_cancel( op: &opendal_operator, @@ -154,7 +156,9 @@ pub unsafe extern "C" fn opendal_operator_presign_read_with_cancel( })) } -/// \brief Presign a write operation with cancellation support. +/// \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, @@ -174,7 +178,9 @@ pub unsafe extern "C" fn opendal_operator_presign_write_with_cancel( })) } -/// \brief Presign a delete operation with cancellation support. +/// \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_delete_with_cancel( op: &opendal_operator, @@ -194,7 +200,9 @@ pub unsafe extern "C" fn opendal_operator_presign_delete_with_cancel( })) } -/// \brief Presign a stat operation with cancellation support. +/// \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, @@ -214,6 +222,7 @@ pub unsafe extern "C" fn opendal_operator_presign_stat_with_cancel( })) } +/// \brief Presign a read operation. #[no_mangle] pub unsafe extern "C" fn opendal_operator_presign_read( op: &opendal_operator, @@ -223,6 +232,7 @@ pub unsafe extern "C" fn opendal_operator_presign_read( 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, @@ -232,6 +242,7 @@ pub unsafe extern "C" fn opendal_operator_presign_write( unsafe { opendal_operator_presign_write_with_cancel(op, path, expire_secs, std::ptr::null()) } } +/// \brief Presign a delete operation. #[no_mangle] pub unsafe extern "C" fn opendal_operator_presign_delete( op: &opendal_operator, @@ -241,6 +252,7 @@ pub unsafe extern "C" fn opendal_operator_presign_delete( unsafe { opendal_operator_presign_delete_with_cancel(op, path, expire_secs, std::ptr::null()) } } +/// \brief Presign a stat operation. #[no_mangle] pub unsafe extern "C" fn opendal_operator_presign_stat( op: &opendal_operator, diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index 87e4a27b3623..370caa8db4cd 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -96,7 +96,9 @@ impl opendal_reader { } } - /// \brief Read data from the reader with cancellation support. + /// \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_read_with_cancel( &mut self, @@ -122,7 +124,9 @@ impl opendal_reader { unsafe { Self::opendal_reader_read_with_cancel(self, buf, len, std::ptr::null()) } } - /// \brief Seek to an offset with cancellation support. + /// \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, diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs index dbe537c8615e..a34a17a896f6 100644 --- a/bindings/c/src/writer.rs +++ b/bindings/c/src/writer.rs @@ -68,7 +68,9 @@ impl opendal_writer { } } - /// \brief Write data to the writer with cancellation support. + /// \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_write_with_cancel( &mut self, @@ -93,7 +95,9 @@ impl opendal_writer { unsafe { Self::opendal_writer_write_with_cancel(self, bytes, std::ptr::null()) } } - /// \brief Close the writer with cancellation support. + /// \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, From 049512a8ce1fc80976bb1a66644ed71e6a4dd885 Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 18:51:49 +0000 Subject: [PATCH 08/13] etract runtime out --- bindings/c/src/cancel.rs | 36 +++++++++++++++++ bindings/c/src/lib.rs | 2 + bindings/c/src/lister.rs | 12 +----- bindings/c/src/operator.rs | 83 +++++++++++++++----------------------- bindings/c/src/presign.rs | 23 ++--------- bindings/c/src/reader.rs | 14 +------ bindings/c/src/runtime.rs | 26 ++++++++++++ bindings/c/src/writer.rs | 14 +------ 8 files changed, 106 insertions(+), 104 deletions(-) create mode 100644 bindings/c/src/runtime.rs diff --git a/bindings/c/src/cancel.rs b/bindings/c/src/cancel.rs index f3dc46c63629..a893bdeee15e 100644 --- a/bindings/c/src/cancel.rs +++ b/bindings/c/src/cancel.rs @@ -24,6 +24,8 @@ use std::task::{Context, Poll, Waker}; use ::opendal as core; +use crate::runtime::RUNTIME; + struct CancelState { cancelled: AtomicBool, waker: Mutex>, @@ -158,3 +160,37 @@ where 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 ca1799421d46..ffbcab98cdd6 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -36,6 +36,8 @@ mod error; pub use error::opendal_code; pub use error::opendal_error; +mod runtime; + mod cancel; pub use cancel::opendal_cancel_token; diff --git a/bindings/c/src/lister.rs b/bindings/c/src/lister.rs index 525dab38eae2..620cbaeb251d 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -18,10 +18,8 @@ use ::opendal as core; use futures_util::StreamExt; use std::ffi::c_void; -use std::future::Future; use super::*; -use crate::operator::RUNTIME; /// \brief BlockingLister is designed to list entries at given path in a blocking /// manner. @@ -52,14 +50,6 @@ impl opendal_lister { } } - fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result - where - F: Future>, - { - let token = unsafe { cancel::clone_token(token) }; - RUNTIME.block_on(cancel::run(token, fut)) - } - fn result_next(result: core::Result>) -> opendal_result_lister_next { match result { Ok(Some(e)) => opendal_result_lister_next { @@ -86,7 +76,7 @@ impl opendal_lister { token: *const opendal_cancel_token, ) -> opendal_result_lister_next { let lister = self.deref_mut(); - Self::result_next(Self::block_on_cancelable(token, async move { + Self::result_next(cancel::block_on_cancelable(token, async move { lister.next().await.transpose() })) } diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index 8d4367a06124..df76ede2b3eb 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -17,21 +17,12 @@ use std::collections::HashMap; use std::ffi::c_void; -use std::future::Future; use std::os::raw::c_char; -use std::sync::LazyLock; use ::opendal as core; use super::*; -pub(crate) 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. /// @@ -133,19 +124,6 @@ fn new_operator_result(op: core::Result) -> opendal_result_opera } } -fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result -where - T: Send + 'static, - F: Future> + Send + 'static, -{ - let token = unsafe { cancel::clone_token(token) }; - RUNTIME - .block_on(RUNTIME.spawn(cancel::run(token, fut))) - .map_err(|err| { - core::Error::new(core::ErrorKind::Unexpected, "cancellable task failed").set_source(err) - })? -} - unsafe fn parse_cstr<'a>(ptr: *const c_char, name: &str) -> &'a str { assert!(!ptr.is_null()); unsafe { std::ffi::CStr::from_ptr(ptr) } @@ -315,7 +293,7 @@ pub unsafe extern "C" fn opendal_operator_write_with_cancel( let path = unsafe { parse_cstr(path, "path") }.to_owned(); let bytes = core::Buffer::from(bytes); let op = op.deref().clone(); - result_error(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.write(&path, bytes).await.map(|_| ()) })) } @@ -339,7 +317,7 @@ pub unsafe extern "C" fn opendal_operator_write_with_options_cancel( unsafe { (&*opts).into() } }; let op = op.deref().clone(); - result_error(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.write_options(&path, bytes, opts).await.map(|_| ()) })) } @@ -355,10 +333,9 @@ pub unsafe extern "C" fn opendal_operator_read_with_cancel( ) -> opendal_result_read { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - result_read(block_on_cancelable( - token, - async move { op.read(&path).await }, - )) + result_read(cancel::block_on_cancelable_spawn(token, async move { + op.read(&path).await + })) } /// \brief Like `opendal_operator_read_with` with cooperative cancellation. @@ -378,7 +355,7 @@ pub unsafe extern "C" fn opendal_operator_read_with_options_cancel( unsafe { (&*opts).into() } }; let op = op.deref().clone(); - result_read(block_on_cancelable(token, async move { + result_read(cancel::block_on_cancelable_spawn(token, async move { op.read_options(&path, opts).await })) } @@ -394,7 +371,7 @@ pub unsafe extern "C" fn opendal_operator_reader_with_cancel( ) -> opendal_result_operator_reader { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { + match cancel::block_on_cancelable_spawn(token, async move { let reader = op.reader(&path).await?; opendal_reader::create_async(reader).await }) { @@ -420,7 +397,7 @@ pub unsafe extern "C" fn opendal_operator_writer_with_cancel( ) -> opendal_result_operator_writer { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.writer(&path).await }) { + 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(), @@ -449,7 +426,10 @@ pub unsafe extern "C" fn opendal_operator_writer_with_options_cancel( unsafe { (&*opts).into() } }; let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.writer_options(&path, opts).await }) { + 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(), @@ -472,10 +452,9 @@ pub unsafe extern "C" fn opendal_operator_delete_with_cancel( ) -> *mut opendal_error { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - result_error(block_on_cancelable( - token, - async move { op.delete(&path).await }, - )) + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.delete(&path).await + })) } /// \brief Like `opendal_operator_delete_with` with cooperative cancellation. @@ -491,7 +470,7 @@ pub unsafe extern "C" fn opendal_operator_delete_with_options_cancel( let path = unsafe { parse_cstr(path, "path") }.to_owned(); let opts = unsafe { parse_delete_options(opts) }; let op = op.deref().clone(); - result_error(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.delete_options(&path, opts).await })) } @@ -508,7 +487,7 @@ pub unsafe extern "C" fn opendal_operator_is_exist_with_cancel( ) -> opendal_result_is_exist { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.exists(&path).await }) { + 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(), @@ -531,7 +510,7 @@ pub unsafe extern "C" fn opendal_operator_exists_with_cancel( ) -> opendal_result_exists { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.exists(&path).await }) { + 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(), @@ -554,10 +533,9 @@ pub unsafe extern "C" fn opendal_operator_stat_with_cancel( ) -> opendal_result_stat { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - result_stat(block_on_cancelable( - token, - async move { op.stat(&path).await }, - )) + result_stat(cancel::block_on_cancelable_spawn(token, async move { + op.stat(&path).await + })) } /// \brief Like `opendal_operator_stat_with` with cooperative cancellation. @@ -577,7 +555,7 @@ pub unsafe extern "C" fn opendal_operator_stat_with_options_cancel( unsafe { (&*opts).into() } }; let op = op.deref().clone(); - result_stat(block_on_cancelable(token, async move { + result_stat(cancel::block_on_cancelable_spawn(token, async move { op.stat_options(&path, opts).await })) } @@ -593,7 +571,7 @@ pub unsafe extern "C" fn opendal_operator_list_with_cancel( ) -> opendal_result_list { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.lister(&path).await }) { + 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(), @@ -618,7 +596,10 @@ pub unsafe extern "C" fn opendal_operator_list_with_options_cancel( let path = unsafe { parse_cstr(path, "path") }.to_owned(); let opts = unsafe { parse_list_options(opts) }; let op = op.deref().clone(); - match block_on_cancelable(token, async move { op.lister_options(&path, opts).await }) { + 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(), @@ -641,7 +622,7 @@ pub unsafe extern "C" fn opendal_operator_create_dir_with_cancel( ) -> *mut opendal_error { let path = unsafe { parse_cstr(path, "path") }.to_owned(); let op = op.deref().clone(); - result_error(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.create_dir(&path).await })) } @@ -659,7 +640,7 @@ pub unsafe extern "C" fn opendal_operator_rename_with_cancel( 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(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.rename(&src, &dest).await })) } @@ -677,7 +658,7 @@ pub unsafe extern "C" fn opendal_operator_copy_with_cancel( 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(block_on_cancelable(token, async move { + result_error(cancel::block_on_cancelable_spawn(token, async move { op.copy(&src, &dest).await.map(|_| ()) })) } @@ -691,7 +672,9 @@ pub unsafe extern "C" fn opendal_operator_check_with_cancel( token: *const opendal_cancel_token, ) -> *mut opendal_error { let op = op.deref().clone(); - result_error(block_on_cancelable(token, async move { op.check().await })) + result_error(cancel::block_on_cancelable_spawn(token, async move { + op.check().await + })) } /// \brief Blocking write raw bytes to `path`. diff --git a/bindings/c/src/presign.rs b/bindings/c/src/presign.rs index 065e1059b152..21058b559939 100644 --- a/bindings/c/src/presign.rs +++ b/bindings/c/src/presign.rs @@ -16,7 +16,6 @@ // under the License. use std::ffi::{c_char, CStr, CString}; -use std::future::Future; use std::time::Duration; use ::opendal as core; @@ -26,7 +25,6 @@ use crate::cancel; use crate::error::opendal_error; use crate::opendal_cancel_token; use crate::operator::opendal_operator; -use crate::operator::RUNTIME; /// \brief The key-value pair for the headers of the presigned request. #[repr(C)] @@ -86,19 +84,6 @@ impl opendal_presigned_request_inner { } } -fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result -where - T: Send + 'static, - F: Future> + Send + 'static, -{ - let token = unsafe { cancel::clone_token(token) }; - RUNTIME - .block_on(RUNTIME.spawn(cancel::run(token, fut))) - .map_err(|err| { - core::Error::new(core::ErrorKind::Unexpected, "cancellable task failed").set_source(err) - })? -} - fn result_presign(result: core::Result) -> opendal_result_presign { match result { Ok(req) => { @@ -151,7 +136,7 @@ pub unsafe extern "C" fn opendal_operator_presign_read_with_cancel( .to_owned(); let duration = Duration::from_secs(expire_secs); let op = op.deref().clone(); - result_presign(block_on_cancelable(token, async move { + result_presign(cancel::block_on_cancelable_spawn(token, async move { op.presign_read(&path, duration).await })) } @@ -173,7 +158,7 @@ pub unsafe extern "C" fn opendal_operator_presign_write_with_cancel( .to_owned(); let duration = Duration::from_secs(expire_secs); let op = op.deref().clone(); - result_presign(block_on_cancelable(token, async move { + result_presign(cancel::block_on_cancelable_spawn(token, async move { op.presign_write(&path, duration).await })) } @@ -195,7 +180,7 @@ pub unsafe extern "C" fn opendal_operator_presign_delete_with_cancel( .to_owned(); let duration = Duration::from_secs(expire_secs); let op = op.deref().clone(); - result_presign(block_on_cancelable(token, async move { + result_presign(cancel::block_on_cancelable_spawn(token, async move { op.presign_delete(&path, duration).await })) } @@ -217,7 +202,7 @@ pub unsafe extern "C" fn opendal_operator_presign_stat_with_cancel( .to_owned(); let duration = Duration::from_secs(expire_secs); let op = op.deref().clone(); - result_presign(block_on_cancelable(token, async move { + result_presign(cancel::block_on_cancelable_spawn(token, async move { op.presign_stat(&path, duration).await })) } diff --git a/bindings/c/src/reader.rs b/bindings/c/src/reader.rs index 370caa8db4cd..33cd34d13e72 100644 --- a/bindings/c/src/reader.rs +++ b/bindings/c/src/reader.rs @@ -16,14 +16,12 @@ // under the License. use std::ffi::c_void; -use std::future::Future; use std::io::SeekFrom; use ::opendal as core; use futures_util::AsyncReadExt; use futures_util::AsyncSeekExt; -use crate::operator::RUNTIME; use crate::result::opendal_result_reader_seek; use super::*; @@ -67,14 +65,6 @@ impl opendal_reader { } } - fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result - where - F: Future>, - { - let token = unsafe { cancel::clone_token(token) }; - RUNTIME.block_on(cancel::run(token, fut)) - } - async fn read_async_inner(reader: &mut AsyncReader, buf: &mut [u8]) -> core::Result { reader .reader @@ -109,7 +99,7 @@ impl opendal_reader { assert!(!buf.is_null()); let buf = std::slice::from_raw_parts_mut(buf, len); let reader = self.deref_mut(); - Self::result_read(Self::block_on_cancelable(token, async move { + Self::result_read(cancel::block_on_cancelable(token, async move { Self::read_async_inner(reader, buf).await })) } @@ -135,7 +125,7 @@ impl opendal_reader { token: *const opendal_cancel_token, ) -> opendal_result_reader_seek { let reader = self.deref_mut(); - match Self::block_on_cancelable(token, async move { + match cancel::block_on_cancelable(token, async move { seek_async_reader(reader, offset, whence).await }) { Ok(pos) => opendal_result_reader_seek { diff --git a/bindings/c/src/runtime.rs b/bindings/c/src/runtime.rs new file mode 100644 index 000000000000..d3c40fbbf8d1 --- /dev/null +++ b/bindings/c/src/runtime.rs @@ -0,0 +1,26 @@ +// 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. +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 a34a17a896f6..c63d4ac65524 100644 --- a/bindings/c/src/writer.rs +++ b/bindings/c/src/writer.rs @@ -17,10 +17,8 @@ use ::opendal as core; use std::ffi::c_void; -use std::future::Future; use super::*; -use crate::operator::RUNTIME; /// \brief The result type returned by opendal's writer operation. /// \note The opendal_writer actually owns a pointer to @@ -47,14 +45,6 @@ impl opendal_writer { } } - fn block_on_cancelable(token: *const opendal_cancel_token, fut: F) -> core::Result - where - F: Future>, - { - let token = unsafe { cancel::clone_token(token) }; - RUNTIME.block_on(cancel::run(token, fut)) - } - fn result_write(result: core::Result) -> opendal_result_writer_write { match result { Ok(size) => opendal_result_writer_write { @@ -80,7 +70,7 @@ impl opendal_writer { let size = bytes.len; let bytes = core::Buffer::from(bytes); let writer = self.deref_mut(); - Self::result_write(Self::block_on_cancelable(token, async move { + Self::result_write(cancel::block_on_cancelable(token, async move { writer.write(bytes).await?; Ok(size) })) @@ -107,7 +97,7 @@ impl opendal_writer { if !ptr.is_null() { let writer = (*ptr).deref_mut(); if let Err(e) = - Self::block_on_cancelable(token, async move { writer.close().await }) + cancel::block_on_cancelable(token, async move { writer.close().await }) { return opendal_error::new(e); } From faa56d12c8d4e3112a6ddb730162fd12920b04c5 Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 22:51:21 +0000 Subject: [PATCH 09/13] update interface --- bindings/c/include/opendal.h | 10 +- bindings/c/src/lister.rs | 8 +- bindings/c/src/operator.rs | 2 +- bindings/go/README.md | 34 ++-- bindings/go/context_test.go | 150 +++++++----------- bindings/go/delete.go | 14 +- bindings/go/ffi.go | 50 +++--- bindings/go/lister.go | 48 +++--- bindings/go/opendal.go | 6 +- bindings/go/operator.go | 20 +-- bindings/go/presign.go | 32 ++-- bindings/go/reader.go | 58 ++++--- bindings/go/stat.go | 20 +-- .../go/tests/behavior_tests/benchmark_test.go | 5 +- bindings/go/tests/behavior_tests/copy_test.go | 43 ++--- .../tests/behavior_tests/create_dir_test.go | 11 +- .../go/tests/behavior_tests/delete_test.go | 37 ++--- bindings/go/tests/behavior_tests/list_test.go | 105 ++++++------ .../go/tests/behavior_tests/opendal_test.go | 3 +- .../go/tests/behavior_tests/presign_test.go | 19 +-- bindings/go/tests/behavior_tests/read_test.go | 85 +++++----- .../go/tests/behavior_tests/rename_test.go | 43 ++--- bindings/go/tests/behavior_tests/stat_test.go | 75 ++++----- .../go/tests/behavior_tests/write_test.go | 81 +++++----- bindings/go/writer.go | 87 +++++----- 25 files changed, 501 insertions(+), 545 deletions(-) diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index e2bf59828ac1..e438d8a97e42 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -1097,7 +1097,13 @@ struct opendal_result_lister_next opendal_lister_next_with_cancel(struct opendal const struct opendal_cancel_token *token); /** - * \brief Return the next object to be listed. + * \brief Return the next object to be listed + * + * Lister is an iterator of the objects under its path, this method is the same as + * calling next() on the iterator + * + * For examples, please see the comment section of opendal_operator_list() + * @see opendal_operator_list() */ struct opendal_result_lister_next opendal_lister_next(struct opendal_lister *self); @@ -2029,7 +2035,7 @@ struct opendal_result_list opendal_operator_list(const struct opendal_operator * * * # Safety * - * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * * The memory pointed to by `path` must contain a valid null terminator at the end of * the string. * * # Panic diff --git a/bindings/c/src/lister.rs b/bindings/c/src/lister.rs index 620cbaeb251d..36c80d246fa6 100644 --- a/bindings/c/src/lister.rs +++ b/bindings/c/src/lister.rs @@ -81,7 +81,13 @@ impl opendal_lister { })) } - /// \brief Return the next object to be listed. + /// \brief Return the next object to be listed + /// + /// Lister is an iterator of the objects under its path, this method is the same as + /// calling next() on the iterator + /// + /// For examples, please see the comment section of opendal_operator_list() + /// @see opendal_operator_list() #[no_mangle] pub unsafe extern "C" fn opendal_lister_next(&mut self) -> opendal_result_lister_next { unsafe { Self::opendal_lister_next_with_cancel(self, std::ptr::null()) } diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index df76ede2b3eb..4be80edc672c 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -1238,7 +1238,7 @@ pub unsafe extern "C" fn opendal_operator_list( /// /// # Safety /// -/// * The memory pointed to by `path` must contain a valid nul terminator at the end of +/// * The memory pointed to by `path` must contain a valid null terminator at the end of /// the string. /// /// # Panic diff --git a/bindings/go/README.md b/bindings/go/README.md index 03ec2161d869..fbcea11ed091 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,23 +81,30 @@ 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 -Methods ending in `WithContext` forward context cancellation to OpenDAL's native -cancel token. When the Go context is canceled, the binding signals cancellation -and returns `context.Canceled` or `context.DeadlineExceeded` immediately. The -native operation may continue briefly in the background until it observes the -cancel token. +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, so they can be safely reused or closed. ## Run Tests diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index ecb2e752e6b3..a26d9d457a50 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -33,110 +33,66 @@ func newCanceledContext() context.Context { return ctx } -func TestOperatorWithContextMethodsReturnCanceledContext(t *testing.T) { +func TestOperatorContextFirstMethodsReturnCanceledContext(t *testing.T) { ctx := newCanceledContext() op := &Operator{} - if _, err := op.ReadWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("ReadWithContext() error = %v, want context.Canceled", err) + if _, err := op.Read(ctx, "path"); !errors.Is(err, context.Canceled) { + t.Fatalf("Read() error = %v, want context.Canceled", err) } - if err := op.WriteWithContext(ctx, "path", []byte("data")); !errors.Is(err, context.Canceled) { - t.Fatalf("WriteWithContext() 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.StatWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("StatWithContext() 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.IsExistWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("IsExistWithContext() 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.DeleteWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("DeleteWithContext() 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.ListWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("ListWithContext() 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.CheckWithContext(ctx); !errors.Is(err, context.Canceled) { - t.Fatalf("CheckWithContext() 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.CreateDirWithContext(ctx, "path/"); !errors.Is(err, context.Canceled) { - t.Fatalf("CreateDirWithContext() 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.ReaderWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("ReaderWithContext() 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.WriterWithContext(ctx, "path"); !errors.Is(err, context.Canceled) { - t.Fatalf("WriterWithContext() 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.CopyWithContext(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { - t.Fatalf("CopyWithContext() 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.RenameWithContext(ctx, "src", "dest"); !errors.Is(err, context.Canceled) { - t.Fatalf("RenameWithContext() 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.PresignReadWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { - t.Fatalf("PresignReadWithContext() 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.PresignWriteWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { - t.Fatalf("PresignWriteWithContext() 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.PresignDeleteWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { - t.Fatalf("PresignDeleteWithContext() 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.PresignStatWithContext(ctx, "path", time.Minute); !errors.Is(err, context.Canceled) { - t.Fatalf("PresignStatWithContext() error = %v, want context.Canceled", err) - } -} - -func TestCancellableReadBufferUsesHeapForCancelableContext(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - buf := make([]byte, 8) - readBuf, apply := cancellableReadBuffer(ctx, buf) - if len(readBuf) != len(buf) { - t.Fatalf("readBuf len = %d, want %d", len(readBuf), len(buf)) - } - if &readBuf[0] == &buf[0] { - t.Fatal("expected heap buffer for cancelable context") - } - - readBuf[0] = 'x' - apply(1) - if buf[0] != 'x' { - t.Fatalf("buf[0] = %q, want %q", buf[0], 'x') - } -} - -func TestCancellableReadBufferReusesCallerBufferForBackgroundContext(t *testing.T) { - buf := make([]byte, 8) - readBuf, apply := cancellableReadBuffer(context.Background(), buf) - if &readBuf[0] != &buf[0] { - t.Fatal("expected caller buffer for non-cancelable context") - } - apply(0) -} - -func TestCancellableWriteBufferCopiesForCancelableContext(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - data := []byte("hello") - copied := cancellableWriteBuffer(ctx, data) - if &copied[0] == &data[0] { - t.Fatal("expected heap copy for cancelable context") - } - copied[0] = 'H' - if data[0] != 'h' { - t.Fatalf("data[0] = %q, want %q", data[0], 'h') + 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} + w := &Writer{inner: inner, cancelCtx: newCanceledContext()} - if err := w.CloseWithContext(newCanceledContext()); !errors.Is(err, context.Canceled) { - t.Fatalf("CloseWithContext() error = %v, want context.Canceled", err) + 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") @@ -149,8 +105,8 @@ func TestWriterCloseIsIdempotent(t *testing.T) { if err := w.Close(); err != nil { t.Fatalf("first Close() = %v, want nil", err) } - if err := w.CloseWithContext(context.Background()); err != nil { - t.Fatalf("second CloseWithContext() = %v, want nil", err) + if err := w.Close(); err != nil { + t.Fatalf("second Close() = %v, want nil", err) } } @@ -204,35 +160,35 @@ func TestWriterCloseReleaseRunsOnce(t *testing.T) { func TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { inner := &opendalWriter{} - w := &Writer{inner: inner} + w := &Writer{inner: inner, cancelCtx: newCanceledContext()} - if err := w.CloseWithContext(newCanceledContext()); !errors.Is(err, context.Canceled) { - t.Fatalf("CloseWithContext() error = %v, want context.Canceled", err) + 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") } } -func TestIOWithContextMethodsReturnCanceledContext(t *testing.T) { +func TestIOHandleMethodsUseBoundCanceledContext(t *testing.T) { ctx := newCanceledContext() - reader := &Reader{} - if _, err := reader.ReadWithContext(ctx, make([]byte, 1)); !errors.Is(err, context.Canceled) { - t.Fatalf("Reader.ReadWithContext() error = %v, want context.Canceled", err) + 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.SeekWithContext(ctx, 0, 0); !errors.Is(err, context.Canceled) { - t.Fatalf("Reader.SeekWithContext() 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{} - if _, err := writer.WriteWithContext(ctx, []byte("data")); !errors.Is(err, context.Canceled) { - t.Fatalf("Writer.WriteWithContext() 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{} - if lister.NextWithContext(ctx) { - t.Fatal("Lister.NextWithContext() = true, want false") + 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/delete.go b/bindings/go/delete.go index 609f040080b3..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,30 +72,26 @@ 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 { - return op.DeleteWithContext(context.Background(), path, opts...) -} - -func (op *Operator) DeleteWithContext(ctx context.Context, path string, opts ...WithDeleteFn) error { +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) diff --git a/bindings/go/ffi.go b/bindings/go/ffi.go index 11cc515c4a55..d2fcf2a2c093 100644 --- a/bindings/go/ffi.go +++ b/bindings/go/ffi.go @@ -44,10 +44,16 @@ type contextResult[T any] struct { // runWithCancelContext forwards Go context cancellation to native OpenDAL calls. // // When the context is canceled, the native cancel token is signaled and this -// helper returns ctx.Err() immediately. The native call may continue briefly in -// the background until it observes cancellation. Callers that pass slice data -// across FFI must use cancellableReadBuffer or cancellableWriteBuffer so the -// background call does not touch caller-owned memory after this returns. +// 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 { @@ -61,10 +67,10 @@ func runWithCancelContext[T any](ctx context.Context, ffiCtx context.Context, fn } token := ffiCancelTokenNew.symbol(ffiCtx)() + defer ffiCancelTokenFree.symbol(ffiCtx)(token) ch := make(chan contextResult[T], 1) go func() { - defer ffiCancelTokenFree.symbol(ffiCtx)(token) value, err := fn(token) ch <- contextResult[T]{value: value, err: err} }() @@ -73,39 +79,19 @@ func runWithCancelContext[T any](ctx context.Context, ffiCtx context.Context, fn 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) - go func() { - result := <-ch - for _, cleanupFn := range cleanup { - if cleanupFn != nil { - cleanupFn(result.value) - } + result := <-ch + for _, cleanupFn := range cleanup { + if cleanupFn != nil { + cleanupFn(result.value) } - }() - return zero, ctx.Err() - } -} - -func cancellableReadBuffer(ctx context.Context, buf []byte) (readBuf []byte, apply func(int)) { - if ctx.Done() == nil || len(buf) == 0 { - return buf, func(int) {} - } - - heap := make([]byte, len(buf)) - return heap, func(n int) { - if n > 0 { - copy(buf, heap[:n]) } + return zero, ctx.Err() } } -func cancellableWriteBuffer(ctx context.Context, data []byte) []byte { - if ctx.Done() == nil || len(data) == 0 { - return data - } - return append([]byte(nil), data...) -} - 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) diff --git a/bindings/go/lister.go b/bindings/go/lister.go index 6de05ebc1308..829bfe48ba12 100644 --- a/bindings/go/lister.go +++ b/bindings/go/lister.go @@ -44,7 +44,7 @@ import ( // # Example // // func exampleCheck(op *opendal.Operator) { -// err = op.Check() +// err = op.Check(context.Background()) // if err != nil { // log.Printf("Operator check failed: %v", err) // } else { @@ -53,11 +53,7 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Check() (err error) { - return op.CheckWithContext(context.Background()) -} - -func (op *Operator) CheckWithContext(ctx context.Context) (err error) { +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) }) @@ -145,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. // @@ -157,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) // } @@ -180,12 +178,9 @@ 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) { - return op.ListWithContext(context.Background(), path, opts...) -} - -func (op *Operator) ListWithContext(ctx context.Context, path string, opts ...WithListFn) (*Lister, error) { +// 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 { @@ -207,8 +202,9 @@ func (op *Operator) ListWithContext(ctx context.Context, path string, opts ...Wi return nil, err } return &Lister{ - inner: listerInner, - ctx: op.ctx, + inner: listerInner, + ctx: op.ctx, + cancelCtx: ctx, }, nil }, func(lister *Lister) { if lister != nil { @@ -248,8 +244,11 @@ func (op *Operator) ListWithContext(ctx context.Context, path string, opts ...Wi 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 @@ -300,12 +299,15 @@ 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 { - return l.NextWithContext(context.Background()) -} - -func (l *Lister) NextWithContext(ctx context.Context) bool { - entry, err := runWithCancelContext(ctx, l.ctx, func(token *opendalCancelToken) (*Entry, error) { + 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 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 9b77ff36e85e..7b4b2401de0a 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 source file path. // - to: The destination file path. // @@ -50,7 +52,7 @@ import ( // # Example // // func exampleCopy(op *operatorCopy) { -// err = op.Copy("path/from/file", "path/to/file") +// err = op.Copy(context.Background(), "path/from/file", "path/to/file") // if err != nil { // log.Printf("Copy operation failed: %v", err) // } else { @@ -59,11 +61,7 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Copy(src, dest string) error { - return op.CopyWithContext(context.Background(), src, dest) -} - -func (op *Operator) CopyWithContext(ctx context.Context, src, dest string) error { +func (op *Operator) Copy(ctx context.Context, src, dest string) error { return runErrWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) error { return ffiOperatorCopyWithCancel.symbol(op.ctx)(op.inner, src, dest, token) }) @@ -75,6 +73,8 @@ func (op *Operator) CopyWithContext(ctx context.Context, src, dest string) error // // # 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. // @@ -91,7 +91,7 @@ func (op *Operator) CopyWithContext(ctx context.Context, src, dest string) error // # 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 { @@ -100,11 +100,7 @@ func (op *Operator) CopyWithContext(ctx context.Context, src, dest string) error // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Rename(src, dest string) error { - return op.RenameWithContext(context.Background(), src, dest) -} - -func (op *Operator) RenameWithContext(ctx context.Context, src, dest string) error { +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) }) diff --git a/bindings/go/presign.go b/bindings/go/presign.go index ebe62b28c506..cf8d973acb14 100644 --- a/bindings/go/presign.go +++ b/bindings/go/presign.go @@ -32,44 +32,36 @@ import ( 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.PresignReadWithContext(context.Background(), path, expire) -} - -func (op *Operator) PresignReadWithContext(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { +// +// 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.PresignWriteWithContext(context.Background(), path, expire) -} - -func (op *Operator) PresignWriteWithContext(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { +// +// 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.PresignDeleteWithContext(context.Background(), path, expire) -} - -func (op *Operator) PresignDeleteWithContext(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { +// +// 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.PresignStatWithContext(context.Background(), path, expire) -} - -func (op *Operator) PresignStatWithContext(ctx context.Context, path string, expire time.Duration) (*http.Request, error) { +// +// 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) }) diff --git a/bindings/go/reader.go b/bindings/go/reader.go index 244de21025a4..8ed32e953f74 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,11 +63,7 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Read(path string, opts ...WithReadFn) ([]byte, error) { - return op.ReadWithContext(context.Background(), path, opts...) -} - -func (op *Operator) ReadWithContext(ctx context.Context, path string, opts ...WithReadFn) ([]byte, error) { +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) { @@ -289,6 +288,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 @@ -300,11 +301,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) // } @@ -323,19 +326,16 @@ 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) { - return op.ReaderWithContext(context.Background(), path) -} - -func (op *Operator) ReaderWithContext(ctx context.Context, path string) (*Reader, error) { +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, + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, } return reader, nil }, func(reader *Reader) { @@ -348,6 +348,10 @@ func (op *Operator) ReaderWithContext(ctx context.Context, path string) (*Reader 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) @@ -392,14 +396,12 @@ 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) { - return r.ReadWithContext(context.Background(), buf) -} - -func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { - readBuf, apply := cancellableReadBuffer(ctx, buf) - n, err := runWithCancelContext(ctx, r.ctx, func(token *opendalCancelToken) (int, error) { - length := uint(len(readBuf)) + return runWithCancelContext(r.cancelCtx, r.ctx, func(token *opendalCancelToken) (int, error) { + length := uint(len(buf)) if length == 0 { return 0, nil } @@ -410,7 +412,7 @@ func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { err error ) for { - size, err = read(r.inner, readBuf[totalSize:], token) + size, err = read(r.inner, buf[totalSize:], token) totalSize += size if size == 0 || err != nil || totalSize >= length { break @@ -421,10 +423,6 @@ func (r *Reader) ReadWithContext(ctx context.Context, buf []byte) (int, error) { } return int(totalSize), err }) - if err == nil { - apply(n) - } - return n, err } // Seek sets the offset for the next Read operation on the reader. @@ -468,12 +466,10 @@ func (r *Reader) ReadWithContext(ctx context.Context, 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 r.SeekWithContext(context.Background(), offset, whence) -} - -func (r *Reader) SeekWithContext(ctx context.Context, offset int64, whence int) (int64, error) { - return runWithCancelContext(ctx, r.ctx, func(token *opendalCancelToken) (int64, error) { + return runWithCancelContext(r.cancelCtx, r.ctx, func(token *opendalCancelToken) (int64, error) { return ffiReaderSeekWithCancel.symbol(r.ctx)(r.inner, offset, whence, token) }) } diff --git a/bindings/go/stat.go b/bindings/go/stat.go index 444174a9ac8b..59149e10abf7 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,11 +65,7 @@ import ( // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Stat(path string, opts ...WithStatFn) (*Metadata, error) { - return op.StatWithContext(context.Background(), path, opts...) -} - -func (op *Operator) StatWithContext(ctx context.Context, path string, opts ...WithStatFn) (*Metadata, error) { +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) @@ -231,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 @@ -241,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) // } @@ -250,11 +250,7 @@ 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 op.IsExistWithContext(context.Background(), path) -} - -func (op *Operator) IsExistWithContext(ctx context.Context, path string) (bool, error) { +func (op *Operator) IsExist(ctx context.Context, path string) (bool, error) { return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (bool, error) { return ffiOperatorIsExistWithCancel.symbol(op.ctx)(op.inner, path, token) }) 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 1e9f33fa157a..a9cc51ad1371 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" "github.com/apache/opendal/bindings/go" @@ -46,13 +47,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) } @@ -61,10 +62,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) } @@ -73,7 +74,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)) } @@ -86,9 +87,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)) } @@ -100,13 +101,13 @@ func testCopyTargetDir(assert *require.Assertions, op *opendal.Operator, 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)) } @@ -114,9 +115,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)) } @@ -124,7 +125,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", @@ -133,9 +134,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) } @@ -143,16 +144,16 @@ 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) } 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 500e5e6d06a2..359a763e37d8 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" @@ -67,9 +68,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), @@ -84,9 +85,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), @@ -100,17 +101,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)) } @@ -118,17 +119,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)) } @@ -136,22 +137,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") } @@ -160,9 +161,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") @@ -171,20 +172,20 @@ func testReadWithRange(assert *require.Assertions, op *opendal.Operator, fixture 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") } @@ -192,20 +193,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") } @@ -213,9 +214,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") @@ -224,9 +225,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) @@ -239,7 +240,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)) } @@ -251,9 +252,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)) } @@ -261,9 +262,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) @@ -272,14 +273,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) @@ -289,7 +290,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") @@ -299,9 +300,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/writer.go b/bindings/go/writer.go index b447fbb35425..856b977ede6a 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -40,6 +40,8 @@ var errWriterClosed = errors.New("writer is closed") // // # 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. @@ -51,19 +53,14 @@ var errWriterClosed = errors.New("writer is closed") // # 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 { - return op.WriteWithContext(context.Background(), path, data, opts...) -} - -func (op *Operator) WriteWithContext(ctx context.Context, path string, data []byte, opts ...WithWriteFn) error { - data = cancellableWriteBuffer(ctx, 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) @@ -265,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 @@ -285,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) // } @@ -293,11 +292,7 @@ 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 op.CreateDirWithContext(context.Background(), path) -} - -func (op *Operator) CreateDirWithContext(ctx context.Context, path string) error { +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) }) @@ -311,6 +306,8 @@ func (op *Operator) CreateDirWithContext(ctx context.Context, 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. // @@ -321,7 +318,7 @@ func (op *Operator) CreateDirWithContext(ctx context.Context, 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) // } @@ -333,11 +330,9 @@ func (op *Operator) CreateDirWithContext(ctx context.Context, path string) error // } // // Note: This example assumes proper error handling and import statements. -func (op *Operator) Writer(path string, opts ...WithWriteFn) (*Writer, error) { - return op.WriterWithContext(context.Background(), path, opts...) -} - -func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ...WithWriteFn) (*Writer, error) { +// 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) @@ -345,8 +340,9 @@ func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ... return nil, err } writer := &Writer{ - inner: inner, - ctx: op.ctx, + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, } return writer, nil } @@ -363,8 +359,9 @@ func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ... return nil, err } writer := &Writer{ - inner: inner, - ctx: op.ctx, + inner: inner, + ctx: op.ctx, + cancelCtx: ctx, } return writer, nil }, func(writer *Writer) { @@ -377,7 +374,18 @@ func (op *Operator) WriterWithContext(ctx context.Context, path string, opts ... type Writer struct { inner *opendalWriter ctx context.Context - mu sync.Mutex + // 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() { @@ -401,17 +409,19 @@ func (w *Writer) free() { // // # 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) // } @@ -419,13 +429,7 @@ func (w *Writer) free() { // // Note: This example assumes proper error handling and import statements. func (w *Writer) Write(p []byte) (n int, err error) { - return w.WriteWithContext(context.Background(), p) -} - -func (w *Writer) WriteWithContext(ctx context.Context, p []byte) (n int, err error) { - if ctx == nil { - ctx = context.Background() - } + ctx := w.cancelContext() if err := ctx.Err(); err != nil { return 0, err } @@ -437,7 +441,6 @@ func (w *Writer) WriteWithContext(ctx context.Context, p []byte) (n int, err err return 0, errWriterClosed } - p = cancellableWriteBuffer(ctx, p) return runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (int, error) { return ffiWriterWriteWithCancel.symbol(w.ctx)(inner, p, token) }) @@ -446,14 +449,12 @@ func (w *Writer) WriteWithContext(ctx context.Context, p []byte) (n int, err err // 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 { - return w.CloseWithContext(context.Background()) -} - -func (w *Writer) CloseWithContext(ctx context.Context) error { - if ctx == nil { - ctx = context.Background() - } + ctx := w.cancelContext() if err := ctx.Err(); err != nil { return err } From 53608cd247ac69c2b94581fffc3296fa2fc99241 Mon Sep 17 00:00:00 2001 From: dentiny Date: Tue, 9 Jun 2026 23:38:37 +0000 Subject: [PATCH 10/13] fix handle lifecycle --- bindings/go/context_test.go | 17 ----------------- bindings/go/lister.go | 4 ++-- bindings/go/reader.go | 4 ++-- bindings/go/writer.go | 27 +++++++++++++++++---------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index a26d9d457a50..e3527d149460 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -22,7 +22,6 @@ package opendal import ( "context" "errors" - "sync" "testing" "time" ) @@ -142,22 +141,6 @@ func TestWriterCloseShouldReleaseAfterClose(t *testing.T) { } } -func TestWriterCloseReleaseRunsOnce(t *testing.T) { - var count int - var releaseOnce sync.Once - release := func() { - releaseOnce.Do(func() { - count++ - }) - } - - release() - release() - if count != 1 { - t.Fatalf("release count = %d, want 1", count) - } -} - func TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { inner := &opendalWriter{} w := &Writer{inner: inner, cancelCtx: newCanceledContext()} diff --git a/bindings/go/lister.go b/bindings/go/lister.go index 829bfe48ba12..73a99f94f6ae 100644 --- a/bindings/go/lister.go +++ b/bindings/go/lister.go @@ -231,7 +231,7 @@ func (op *Operator) List(ctx context.Context, path string, opts ...WithListFn) ( // // # Example // -// lister, err := op.List("path/to/list") +// lister, err := op.List(context.Background(), "path/to/list") // if err != nil { // log.Fatal(err) // } @@ -290,7 +290,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) // } diff --git a/bindings/go/reader.go b/bindings/go/reader.go index 8ed32e953f74..784d3ea6f120 100644 --- a/bindings/go/reader.go +++ b/bindings/go/reader.go @@ -377,7 +377,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) // } @@ -444,7 +444,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) // } diff --git a/bindings/go/writer.go b/bindings/go/writer.go index 856b977ede6a..cc7dd907cc5f 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -468,21 +468,28 @@ func (w *Writer) Close() error { w.inner = nil w.mu.Unlock() - var releaseOnce sync.Once - release := func() { - releaseOnce.Do(func() { - ffiWriterFree.symbol(w.ctx)(inner) - }) - } - _, err := runWithCancelContext(ctx, w.ctx, func(token *opendalCancelToken) (struct{}, error) { return struct{}{}, ffiWriterCloseWithCancel.symbol(w.ctx)(inner, token) - }, func(struct{}) { - release() }) if shouldReleaseWriterAfterClose(err) { - release() + // 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. + w.mu.Lock() + if w.inner == nil { + w.inner = inner + } else { + // A concurrent operation already repopulated the handle; free ours to + // avoid a leak. + ffiWriterFree.symbol(w.ctx)(inner) } + w.mu.Unlock() return err } From b09b9fa8c0665216fc20e0bd3f44fd90ceb4a70f Mon Sep 17 00:00:00 2001 From: dentiny Date: Wed, 10 Jun 2026 07:22:16 +0000 Subject: [PATCH 11/13] address fable comment --- bindings/c/src/cancel.rs | 3 ++- bindings/c/src/runtime.rs | 5 +++++ bindings/go/README.md | 12 +++++++++++- bindings/go/context_test.go | 22 ++++++++++++++++++++++ bindings/go/lister.go | 7 +++++++ bindings/go/reader.go | 6 ++++++ bindings/go/stat.go | 14 ++++++++------ bindings/go/types.go | 16 ++++++++++++++++ bindings/go/writer.go | 22 +++++++++++++++------- 9 files changed, 92 insertions(+), 15 deletions(-) diff --git a/bindings/c/src/cancel.rs b/bindings/c/src/cancel.rs index a893bdeee15e..eefd53b998bb 100644 --- a/bindings/c/src/cancel.rs +++ b/bindings/c/src/cancel.rs @@ -149,8 +149,9 @@ where match token { Some(token) => { tokio::select! { - result = fut => result, + biased; _ = token.cancelled() => Err(cancelled_error()), + result = fut => result, } } None => fut.await, diff --git a/bindings/c/src/runtime.rs b/bindings/c/src/runtime.rs index d3c40fbbf8d1..c6b0cc3bdb95 100644 --- a/bindings/c/src/runtime.rs +++ b/bindings/c/src/runtime.rs @@ -18,6 +18,11 @@ 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() diff --git a/bindings/go/README.md b/bindings/go/README.md index fbcea11ed091..1f3fb6c1fdbd 100644 --- a/bindings/go/README.md +++ b/bindings/go/README.md @@ -104,7 +104,17 @@ 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, so they can be safely reused or closed. +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 diff --git a/bindings/go/context_test.go b/bindings/go/context_test.go index e3527d149460..b194c65f87de 100644 --- a/bindings/go/context_test.go +++ b/bindings/go/context_test.go @@ -153,6 +153,28 @@ func TestWriterDeferredCloseAfterPreCancelledClose(t *testing.T) { } } +// 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() diff --git a/bindings/go/lister.go b/bindings/go/lister.go index 73a99f94f6ae..26a0c30e8389 100644 --- a/bindings/go/lister.go +++ b/bindings/go/lister.go @@ -241,6 +241,13 @@ func (op *Operator) List(ctx context.Context, path string, opts ...WithListFn) ( // // 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 diff --git a/bindings/go/reader.go b/bindings/go/reader.go index 784d3ea6f120..369fd15efadb 100644 --- a/bindings/go/reader.go +++ b/bindings/go/reader.go @@ -345,6 +345,12 @@ func (op *Operator) Reader(ctx context.Context, path string) (*Reader, error) { }) } +// 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 diff --git a/bindings/go/stat.go b/bindings/go/stat.go index 59149e10abf7..6ebc36671fa2 100644 --- a/bindings/go/stat.go +++ b/bindings/go/stat.go @@ -252,7 +252,7 @@ func newOpendalStatOptions(ctx context.Context, o *statOptions) (*opendalStatOpt // } func (op *Operator) IsExist(ctx context.Context, path string) (bool, error) { return runWithCancelContext(ctx, op.ctx, func(token *opendalCancelToken) (bool, error) { - return ffiOperatorIsExistWithCancel.symbol(op.ctx)(op.inner, path, token) + return ffiOperatorExistsWithCancel.symbol(op.ctx)(op.inner, path, token) }) } @@ -305,9 +305,11 @@ var ffiOperatorStatWithOptionsCancel = newFFI(ffiOpts{ } }) -var ffiOperatorIsExistWithCancel = newFFI(ffiOpts{ - sym: "opendal_operator_is_exist_with_cancel", - rType: &typeResultIsExist, +// 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) { @@ -315,7 +317,7 @@ var ffiOperatorIsExistWithCancel = newFFI(ffiOpts{ if err != nil { return false, err } - var result resultIsExist + var result resultExists ffiCall( unsafe.Pointer(&result), unsafe.Pointer(&op), @@ -325,7 +327,7 @@ var ffiOperatorIsExistWithCancel = newFFI(ffiOpts{ 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/types.go b/bindings/go/types.go index be67f1f78560..e85a1a44eeab 100644 --- a/bindings/go/types.go +++ b/bindings/go/types.go @@ -135,6 +135,16 @@ var ( }[0], } + // typeResultExists mirrors opendal_result_exists { bool exists; opendal_error* error; } + typeResultExists = ffi.Type{ + Type: ffi.Struct, + Elements: &[]*ffi.Type{ + &ffi.TypeUint8, + &ffi.TypePointer, + nil, + }[0], + } + typeResultPresign = ffi.Type{ Type: ffi.Struct, Elements: &[]*ffi.Type{ @@ -305,6 +315,12 @@ type resultIsExist struct { error *opendalError } +// resultExists mirrors opendal_result_exists from the non-deprecated exists API. +type resultExists struct { + exists uint8 + error *opendalError +} + type resultStat struct { meta *opendalMetadata error *opendalError diff --git a/bindings/go/writer.go b/bindings/go/writer.go index cc7dd907cc5f..ceabbf07d3a0 100644 --- a/bindings/go/writer.go +++ b/bindings/go/writer.go @@ -371,6 +371,16 @@ func (op *Operator) Writer(ctx context.Context, path string, opts ...WithWriteFn }) } +// 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 @@ -481,14 +491,12 @@ func (w *Writer) Close() error { // 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() - if w.inner == nil { - w.inner = inner - } else { - // A concurrent operation already repopulated the handle; free ours to - // avoid a leak. - ffiWriterFree.symbol(w.ctx)(inner) - } + w.inner = inner w.mu.Unlock() return err } From e25ef6075dd681750b35efa2344f4bb2de9389d7 Mon Sep 17 00:00:00 2001 From: dentiny Date: Wed, 10 Jun 2026 07:30:59 +0000 Subject: [PATCH 12/13] remove unused --- bindings/go/types.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/bindings/go/types.go b/bindings/go/types.go index e85a1a44eeab..e6b04fd9791e 100644 --- a/bindings/go/types.go +++ b/bindings/go/types.go @@ -126,15 +126,6 @@ var ( }[0], } - typeResultIsExist = ffi.Type{ - Type: ffi.Struct, - Elements: &[]*ffi.Type{ - &ffi.TypeUint8, - &ffi.TypePointer, - nil, - }[0], - } - // typeResultExists mirrors opendal_result_exists { bool exists; opendal_error* error; } typeResultExists = ffi.Type{ Type: ffi.Struct, @@ -310,11 +301,6 @@ 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 From 8abe11872f50cf6eb097cf8fd9ff67ed4dca51cb Mon Sep 17 00:00:00 2001 From: dentiny Date: Wed, 10 Jun 2026 08:51:49 +0000 Subject: [PATCH 13/13] fmt --- bindings/c/src/operator.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs index 5b1c0abc23c7..4a3c7164e2eb 100644 --- a/bindings/c/src/operator.rs +++ b/bindings/c/src/operator.rs @@ -655,9 +655,7 @@ pub unsafe extern "C" fn opendal_operator_copy_with_cancel( 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) - } + unsafe { opendal_operator_copy_with_options_cancel(op, src, dest, std::ptr::null(), token) } } /// \brief Like `opendal_operator_copy_with` with cooperative cancellation.