Crate spawns

Source
Expand description

spawns proposes thread context task spawner to ease async runtime agnostic coding.

It introduces few concepts for async runtimes to setup thread context task spawner:

With above, spawns provides spawn() and JoinHandle to spawn and join tasks.

Below is a minimum runtime agnostic echo server. All you have to do for it to be function is setting up thread context task spawner.

use async_net::*;
use futures_lite::io;

pub async fn echo_server(port: u16) {
    let listener = TcpListener::bind(("127.0.0.1", port)).await.unwrap();
    println!("Listen on port: {}", listener.local_addr().unwrap().port());
    let mut echos = vec![];
    let mut id_counter = 0;
    loop {
        let (stream, remote_addr) = listener.accept().await.unwrap();
        id_counter += 1;
        let id = id_counter;
        let handle = spawns::spawn(async move {
            eprintln!("{:010}[{}]: serving", id, remote_addr);
            let (reader, writer) = io::split(stream);
            match io::copy(reader, writer).await {
                Ok(_) => eprintln!("{:010}[{}]: closed", id, remote_addr),
                Err(err) => eprintln!("{:010}[{}]: {:?}", id, remote_addr, err),
            }
        })
        .attach();
        echos.push(handle);
    }
}

§Compatibility with existing async runtimes

This is an open world, there might be tens async runtimes. spawns provides features to inject spawners for few.

  • tokio: uses tokio::runtime::Handle::try_current() to detect thread local tokio runtime handle.
  • smol: uses smol::spawn to spawn task in absent of thread local spawners.
  • async-global-executor: uses async_global_executor::spawn to spawn task in absent of thread local spawners.

For other async runtimes, one could inject Compats to COMPATS themselves.

Noted that, all those compatibility features, injections should only active on tests and binaries. Otherwise, they will be propagated to dependents with unnecessary dependencies.

For macOS users, you may have to put below to your Cargo.toml for above to function.

[profile.dev]
lto = "thin"

See https://github.com/dtolnay/linkme/issues/61 for sure.

§Dealing with multiple global executors

Global executor cloud spawn task with no help from thread context. But this exposes us an dilemma to us, which one to use if there are multiple global executors present ? By default, spawns randomly chooses one and stick to it to spawn tasks in absent of thread context spawners. Generally, this should be safe as global executors should be designed to spawn everywhere. If this is not the case, one could use environment variable SPAWNS_GLOBAL_SPAWNER to specify one. As a safety net, feature panic-multiple-global-spawners is provided to panic if there are multiple global candidates.

Structs§

Blocking
Executor construct to block future until completion.
Builder
Builder to spawn task with more options.
CancelHandle
Handle to cancel associated task.
Id
Unique among running tasks.
JoinError
Error in polling JoinHandle.
JoinHandle
Handle to join or cancel associated task.
Name
Task name.
SpawnScope
Scope where tasks are spawned through given Spawn.
Task
Thin wrapper around task to accommodate possible new members.
TaskHandle
An owned permission to cancel task on Drop besides join and cancel on demand.

Enums§

Compat
Compat encapsulate functions to find async runtimes to spawn task.

Statics§

COMPATS
DistributedSlice to collect Compats.

Traits§

Spawn
Trait to spawn task.

Functions§

block_on
Blocks current thread and runs given future until completion.
enter
Enters a scope where new tasks will be spawned through given Spawn.
id
Gets running task’s id. Panic if no reside in managed async task.
spawn
Spawns a new task.
try_id
Gets running task’s id. None if no reside in managed async task.