#async-executor #async-task #executor #async #future #events

no-std extern_executor

Async executor which delegates futures polling to external event-loop

2 releases

0.1.2 Apr 19, 2020
0.1.0 Apr 17, 2020

#1741 in Asynchronous

MIT license

26KB
416 lines

External executor for async Rust

This project aims to provide simple executor which helps to delegate running asynchronous Rust code to external event loops. As example, it may be useful in case when you develop dynamic linked libraries which have async code in Rust and want to run it in different execution environments.

Usage

On a Rust side you should add extern_executor as dependency to your cdylib crate and use spawn() function to run futures, like so:

use extern_executor::spawn;

spawn(async {
  // your awaits
});

On a C side you should implement executor's driver using your preferred event loop API. For example, when libuv is used it may looks like so:

#include <uv.h>
#include <rust_async_executor.h>

static void task_wake(RustAsyncExecutorExternTask data) {
    uv_async_t* handle = data;
    // wakeup uv's async task
    uv_async_send(handle);
}

static void task_poll(uv_async_t* handle) {
    // poll internal task until task complete
    if (!rust_async_executor_poll(handle->data)) {
        // drop internal task when task complete
        rust_async_executor_drop(handle->data);
        // drop uv's async task handle
        uv_close((uv_handle_t*)handle, NULL);
    }
}

static RustAsyncExecutorExternTask
task_new(RustAsyncExecutorUserData data) {
    uv_loop_t* loop = data;
    // crate and initialize uv's async task handle
    uv_async_t* handle = malloc(sizeof(uv_async_t));
    uv_async_init(loop, handle, task_poll);
    return handle;
}

static void task_run(RustAsyncExecutorExternTask task,
                     RustAsyncExecutorInternTask data) {
    uv_async_t* handle = task;
    // store internal task handle to be able to poll it later
    handle->data = data;
    uv_async_send(handle); // do initial polling (important)
}

void uv_rust_async_executor_init(uv_loop_t *loop) {
    // send out executor API to Rust side
    rust_async_executor_init(task_new, task_run, task_wake, loop);
}

Now you can run your async code in libuv's event loop like so:

int main(void) {
    uv_loop_t loop;

    uv_loop_init(&loop);
    uv_rust_async_executor_init(&loop);

    my_async_function(my_async_callback);

    uv_run(&loop, UV_RUN_DEFAULT);
    uv_loop_close(&loop);

    return 0;
}

The C header rust_async_executor.h generated using cbindgen. There are two options how you can get it:

  • Copy from include directory in this repo
  • Generate by youself by using cbindgen feature

In second case generated header will be available at target/$PROFILE/include directory.

Built-in event-loop drivers

To simplify setup for some widely used event loops the built-in drivers was introduced. To use driver you should enable corresponding feature. Currently supported next drivers:

  • uv built-in libuv event loop integration (see example_uv)
  • dart built-in dart-lang event loop integration (see example_dart)

Linking issues

Rust currently have an issues related to re-exporting of symbols from crate's dependencies (#2771).

As temporary solution you can setup build profile like so:

[profile.release]
lto = true
incremental = false

Tokio compatibility

This executor incompatible with tokio's futures because tokio still has non-trivial executor which mixed with reactor.

Dependencies

~0–1.1MB
~10K SLoC