Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion bindings/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,23 @@ target_link_libraries(basic opendal_c_shared)
add_executable(error_handle examples/error_handle.c)
target_link_libraries(error_handle opendal_c_shared)

add_executable(async_stat examples/async_stat.c)
target_link_libraries(async_stat opendal_c_shared pthread)

add_executable(compare_sync_async examples/compare_sync_async.c)
target_link_libraries(compare_sync_async opendal_c_shared pthread)

# test targets
set(GTEST_SRCS
tests/async_stat_test.cpp
tests/bdd.cpp
tests/error_msg.cpp
tests/list.cpp
tests/opinfo.cpp
tests/reader.cpp
)
add_executable(tests ${GTEST_SRCS})
target_link_libraries(tests opendal_c_shared gtest_main uuid)
target_link_libraries(tests opendal_c_shared gtest_main uuid pthread)
if (TEST_ENABLE_ASAN)
target_compile_options(tests PRIVATE -fsanitize=address)
target_link_options(tests PRIVATE -fsanitize=address)
Expand Down
1 change: 1 addition & 0 deletions bindings/c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ cbindgen = "0.29.0"

[dependencies]
bytes = "1.4.0"
futures = "0.3"
# this crate won't be published, we always use the local version
opendal = { version = ">=0", path = "../../core", features = ["blocking"] }
tokio = { version = "1.27", features = ["fs", "macros", "rt-multi-thread"] }
Expand Down
66 changes: 65 additions & 1 deletion bindings/c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,73 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

```sh
cd build
make basic error_handle
make basic error_handle async_stat compare_sync_async
```

- The `compare_sync_async` example prints the same write/read/delete flow with both
blocking and async operators so you can see the API differences in one run.

## Async APIs

OpenDAL’s C binding mirrors the Rust async operator, but keeps all runtime management on the Rust side so C callers never need to embed Tokio. The design is intentionally future/await centric:

- `opendal_async_operator_new` builds an async operator that internally holds a clone of the core `Operator` plus a handle to OpenDAL’s shared Tokio runtime.
- Each async method (`*_stat`, `*_write`, `*_read`, `*_delete`) immediately returns an opaque `opendal_future_*` handle. Creating the future is non-blocking—the runtime schedules the real work on its thread pool.
- You stay in control of when to pull the result. Call `opendal_future_*_await` to block the current thread until the operation finishes, or `opendal_future_*_poll` to integrate with your own event loop without blocking.
- If you abandon an operation, call `opendal_future_*_free` to cancel it. This aborts the underlying task and drops any pending output safely.

Because futures carry ownership of the eventual metadata/error objects, the `*_await` helpers always transfer heap allocations using the same conventions as the blocking API (free metadata with `opendal_metadata_free`, free errors with `opendal_error_free`, etc.).

### Usage example

Below is a full async stat sequence that starts the request, performs other work, then awaits the result. The same pattern applies to read/write/delete by swapping the function names.

```c
#include "opendal.h"
#include <stdio.h>
#include <unistd.h>

static void sleep_ms(unsigned int ms) { usleep(ms * 1000); }

int main(void) {
opendal_result_operator_new res = opendal_async_operator_new("memory", NULL);
if (res.error) {
fprintf(stderr, "create async op failed: %d\n", res.error->code);
opendal_error_free(res.error);
return 1;
}
const opendal_async_operator* op = (const opendal_async_operator*)res.op;

opendal_result_future_stat fut = opendal_async_operator_stat(op, "missing.txt");
if (fut.error) {
fprintf(stderr, "stat future failed: %d\n", fut.error->code);
opendal_error_free(fut.error);
opendal_async_operator_free(op);
return 1;
}

printf("stat scheduled, doing other work...\n");
sleep_ms(500); // keep UI/event loop responsive while I/O runs

opendal_result_stat out = opendal_future_stat_await(fut.future);
if (out.error) {
printf("stat failed as expected: %d\n", out.error->code);
opendal_error_free(out.error);
} else {
printf("stat succeeded, size=%llu\n",
(unsigned long long)opendal_metadata_content_length(out.meta));
opendal_metadata_free(out.meta);
}

opendal_async_operator_free(op);
return 0;
}
```

Need non-blocking integration with your own loop? Call `opendal_future_stat_poll(fut.future, &out)` inside your loop. It returns `OPENDAL_FUTURE_PENDING` until the result is ready; once it reports `OPENDAL_FUTURE_READY`, call `opendal_future_stat_await` exactly once to consume the output.

See `examples/async_stat.c` for a narrated walkthrough and `tests/async_stat_test.cpp` for GoogleTest-based assertions that cover both success and error paths.

## Documentation

The documentation index page source is under `./docs/doxygen/html/index.html`.
Expand Down
2 changes: 1 addition & 1 deletion bindings/c/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ no_includes = true
sys_includes = ["stdint.h", "stddef.h", "stdbool.h"]

[parse]
include = ["opendal"]
include = ["opendal_c"]
parse_deps = true

[fn]
Expand Down
144 changes: 144 additions & 0 deletions bindings/c/examples/async_stat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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.
*/

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "opendal.h"

int main(int argc, char* argv[])
{
printf("Starting OpenDAL async C example...\n");

// Create a new async operator for the "memory" service
// No options needed for memory backend
printf("Creating async operator for 'memory' backend...\n");
opendal_result_operator_new result_op = opendal_async_operator_new("memory", NULL);
if (result_op.error != NULL) {
// Use %.*s to print the message safely as it's not null-terminated
printf("Error creating operator: %.*s (Code: %d)\n",
(int)result_op.error->message.len,
(char*)result_op.error->message.data,
result_op.error->code);
opendal_error_free(result_op.error);
return 1;
}

// IMPORTANT: Cast the operator pointer from the result struct
// The opendal_result_operator_new struct is reused, but for async,
// the `op` field points to an opendal_async_operator.
const opendal_async_operator* op = (const opendal_async_operator*)result_op.op;
assert(op != NULL);
printf("Async operator created successfully.\n");

// --- Async await-style API ---
const char* path = "non_existent_file.txt";
printf("Calling async stat (future) for path: %s\n", path);

opendal_result_future_stat future_result = opendal_async_operator_stat(op, path);
if (future_result.error != NULL) {
printf("Error creating future: %.*s (Code: %d)\n",
(int)future_result.error->message.len,
(char*)future_result.error->message.data,
future_result.error->code);
opendal_error_free(future_result.error);
printf("Cleaning up resources...\n");
opendal_async_operator_free(op);
printf("OpenDAL async C example finished with errors.\n");
return 1;
}

opendal_result_stat stat_result = opendal_future_stat_await(future_result.future);
if (stat_result.error != NULL) {
printf("Await failed as expected for non-existent file (future API).\n");
printf("Error: %.*s (Code: %d)\n",
(int)stat_result.error->message.len,
(char*)stat_result.error->message.data,
stat_result.error->code);
assert(stat_result.error->code == OPENDAL_NOT_FOUND);
opendal_error_free(stat_result.error);
} else if (stat_result.meta != NULL) {
// Should not happen in this example
opendal_metadata_free(stat_result.meta);
}

// --- Async write/read/delete demo ---
const char* write_path = "greeting.txt";
const char* message = "hi from async write";
opendal_bytes data = {
.data = (uint8_t*)message,
.len = strlen(message),
.capacity = strlen(message),
};

printf("Writing '%s' to %s asynchronously...\n", message, write_path);
opendal_result_future_write write_future = opendal_async_operator_write(op, write_path, &data);
if (write_future.error != NULL) {
printf("Write future creation failed: %.*s\n", (int)write_future.error->message.len, (char*)write_future.error->message.data);
opendal_error_free(write_future.error);
} else {
opendal_error* write_err = opendal_future_write_await(write_future.future);
if (write_err != NULL) {
printf("Write failed: %.*s\n", (int)write_err->message.len, (char*)write_err->message.data);
opendal_error_free(write_err);
} else {
printf("Write completed. Reading it back asynchronously...\n");
opendal_result_future_read read_future = opendal_async_operator_read(op, write_path);
if (read_future.error != NULL) {
printf("Read future creation failed: %.*s\n", (int)read_future.error->message.len, (char*)read_future.error->message.data);
opendal_error_free(read_future.error);
} else {
opendal_result_read read_result = opendal_future_read_await(read_future.future);
if (read_result.error != NULL) {
printf("Read failed: %.*s\n", (int)read_result.error->message.len, (char*)read_result.error->message.data);
opendal_error_free(read_result.error);
} else {
printf("Read back %zu bytes: %.*s\n", read_result.data.len, (int)read_result.data.len, read_result.data.data);
opendal_bytes_free(&read_result.data);
}
}

printf("Deleting %s asynchronously...\n", write_path);
opendal_result_future_delete delete_future = opendal_async_operator_delete(op, write_path);
if (delete_future.error != NULL) {
printf("Delete future creation failed: %.*s\n", (int)delete_future.error->message.len, (char*)delete_future.error->message.data);
opendal_error_free(delete_future.error);
} else {
opendal_error* delete_err = opendal_future_delete_await(delete_future.future);
if (delete_err != NULL) {
printf("Delete failed: %.*s\n", (int)delete_err->message.len, (char*)delete_err->message.data);
opendal_error_free(delete_err);
} else {
printf("Delete completed.\n");
}
}
}
}

// --- Cleanup ---
printf("Cleaning up resources...\n");

// Free the operator
opendal_async_operator_free(op);

printf("OpenDAL async C example finished.\n");
return 0;
}
Loading
Loading