[][src]Crate async_scoped

Enables controlled spawning of non-'static futures when using the async-std executor. Note that this idea is similar to crossbeam::scope, and rayon::scope but asynchronous.

Motivation

Executors like async_std, tokio, etc. support spawning 'static futures onto a thread-pool. However, it is often useful to spawn futures that may not be 'static.

While the future combinators such as for_each_concurrent offer concurrency, they are bundled as a single Task structure by the executor, and hence are not driven parallelly.

Scope API

We propose an API similar to crossbeam::scope to allow spawning futures that are not 'static. The key API is approximately:

This example is not tested
pub unsafe fn scope<'a, T: Send + 'static,
             F: FnOnce(&mut Scope<'a, T>)>(f: F)
             -> impl Stream {
    // ...
}

See scope for the exact definition, and safety guidelines. The simplest and safest API is scope_and_block, used as follows:

#[async_std::test]
async fn scoped_futures() {
    let not_copy = String::from("hello world!");
    let not_copy_ref = &not_copy;
    let (foo, outputs) = async_scoped::scope_and_block(|s| {
        for _ in 0..10 {
            let proc = || async {
                assert_eq!(not_copy_ref, "hello world!");
            };
            s.spawn(proc());
        }
        42
    });
    assert_eq!(foo, 42);
    assert_eq!(outputs.len(), 10);
}

The scope_and_block function above blocks the current thread until all spawned futures are driven in order to guarantee safety.

We also provide an unsafe scope_and_collect, which is asynchronous, and does not block the current thread. However, the user should ensure that the returned future is not forgetten before being driven to completion.

Cancellation

To support cancellation, Scope provides a [spawn_cancellable][spawn_cancellable] which wraps a future to make it cancellable. When a Scope is dropped, (or if cancel method is invoked), all the cancellable futures are scheduled for cancellation. In the next poll of the futures, they are dropped and a default value (provided by a closure during spawn) is returned as the output of the future.

Note: this is an abrupt, hard cancellation. It also requires a reasonable behaviour: futures that do not return control to the executor cannot be cancelled once it has started.

Safety Considerations

The scope API provided in this crate is unsafe as it is possible to forget the stream received from the API without driving it to completion. The only completely (without any additional assumptions) safe API is the scope_and_block function, which blocks the current thread until all spawned futures complete.

The scope_and_block may not be convenient in an asynchronous setting. In this case, the scope_and_collect API may be used. Care must be taken to ensure the returned future is not forgotten before being driven to completion.

Note that dropping this future will lead to it being driven to completion, while blocking the current thread to ensure safety. However, it is unsafe to forget this future before it is fully driven.

Implementation

Our current implementation simply uses unsafe glue to transmute the lifetime, to actually spawn the futures in the executor. The original lifetime is recorded in the Scope. This allows the compiler to enforce the necessary lifetime requirements as long as this returned stream is not forgotten.

For soundness, we drive the stream to completion in the Drop impl. The current thread is blocked until the stream is fully driven.

Unfortunately, since the std::mem::forget method is allowed in safe Rust, the purely asynchronous API here is inherently unsafe.

Structs

Scope

A scope to allow controlled spawning of non 'static futures. Futures can be spawned using spawn or spawn_cancellable methods.

Functions

scope

Creates a Scope to spawn non-'static futures. The function is called with a block which takes an &mut Scope. The spawn method on this arg. can be used to spawn "local" futures.

scope_and_block

A function that creates a scope and immediately awaits, blocking the current thread for spawned futures to complete. The outputs of the futures are collected as a Vec and returned along with the output of the block.

scope_and_collect

An asynchronous function that creates a scope and immediately awaits the stream. The outputs of the futures are collected as a Vec and returned along with the output of the block.