Crate worst_executor

source ·
Expand description

The worst async executor you could think of.

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 and is literally 14 lines of code.

This can become more useful using the core::future::join macro which allows you to wait on multiple futures at once (each time polling a different future).

Currently this library requires rust nightly for the pin macro, the join macro(used in tests) and the const_waker feature (required for complete optimization of block_on)

Examples

Block on a simple future

use worst_executor::block_on;

let val = block_on(async { 42 });
assert_eq!(val, 42);

Receive and send messages through a channel

This works in an event-loop style, each time checking if it can send or recieve a message.

#![feature(future_join)]
use core::future::join;
use worst_executor::block_on;

let (sender, receiver) = async_channel::bounded(1);
block_on(async {
    let send_20_fut = async {
        for i in 0..20 {
            sender.send(i).await.unwrap();
        }
    };
    let recv_20_fut = async {
        for i in 0..20 {
            assert_eq!(receiver.recv().await.unwrap(), i);
        }
    };
    join!(send_20_fut, recv_20_fut).await;
});

A 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()
}

Functions

Runs the given future to completion on the current thread. This function will block the current thread until the future is resolved. The way it works is by “spin-looping” over the poll method until it is ready.