#future #async-executor #async #no-std #poll-ready

nightly no-std worst-executor

A spin-loop like executor for async

2 releases

0.1.1 Jan 26, 2023
0.1.0 Jan 26, 2023

#1622 in Asynchronous

MIT/Apache

18KB
195 lines

Worst Executor

Build status Latest version License dependency status

The simplest async executor possible.
This crate provides a single function, block_on, which takes a future and blocks the current thread until the future is resolved.
The way it works is by "spin-looping" over the poll method until it is ready.

The nice thing about this is that it optimizes very well, for example worst_executor::block_on(async { 42 }) compiles to a single mov instruction.

The bad thing about this is that it does not actually do any scheduling, meaning that if you wait on a future that never resolves, your program will hang. which is why you should probably not use this.

Note that because of its simplicity, the library only uses core and does not require std or alloc nor does it have any dependencies, and is literally 16 lines of code.

Usage

Add this to your Cargo.toml:

[dependencies]
worst-executor = "0.1"

Then in your main.rs:


fn main() {
    worst_executor::block_on(async {
        // Your async code goes here.
    });
}

Use cases

Say you're using a library which returns a future, but you know it does no I/O, and you want to use it in a synchronous context without bringing in a full async runtime like tokio/smol/async-std
You can use worst_executor::block_on to block the current thread until the future is resolved.

Another scenario can be that you want to run an "event loop" in a single thread using async rust. so you can use the core::future::join macro together with the futures::select macro to handle the control flow of your program, while always running on a single thread and never yielding.

Single threaded tcp server

use async_net::{TcpListener, TcpStream};
use futures::{stream::FuturesUnordered, AsyncReadExt, AsyncWriteExt, StreamExt};
use worst_executor::block_on;

block_on(async {
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
    let mut connection_handlers = FuturesUnordered::new();
    // This stream is infinite so it's OK to call fuse.
    let mut listener = listener.incoming().fuse();
    loop {
        futures::select! {
            new_connection = listener.select_next_some() => connection_handlers.push(handle_connection(new_connection?)),
            socket = connection_handlers.select_next_some() =>
                if let Some(socket) = socket {
                    connection_handlers.push(handle_connection(socket));
                },
        }
    }
})

async fn handle_connection(mut stream: TcpStream) -> Option<TcpStream> {
    let mut buf = [0u8; 1024];
    let n = match stream.read(&mut buf).await {
        Ok(n) if n == 0 => return Some(stream),
        Ok(n) => n,
        Err(e) => {
            eprintln!("failed to read from the socket {e:?}");
            return None;
        }
    };
    // Write the data back
    stream.write_all(&buf[0..n]).await
            .map_err(|e| eprintln!("failed to write to the socket {e:?}"))
            .map(|()| stream)
            .ok()
}

No runtime deps