Proof the Runtime

After we have implemented our own simple runtime / executor to support the async implementation paradigm it's time to proof that it is really working. As we might do this without requiring to deploy this part to any actual embedded hardware we can execute this test within a normal operating system environment.

To do so we use a small binary crate utilizing our Brain implementation. The presence of different cores can be simulated by running different threads.

So the main function in this validation will look like this:

fn main() {
    println!("Hello, world!");

    let brain = Arc::new(
        brain::Brain::default()
    );

    // assume we have 4 cores on the target system -> spawn 3 threads
    // the current thread is the 4th core though
    for _ in 0..3 {
        let cloned_brain = Arc::clone(&brain);
        std::thread::spawn( move || {
            loop {
                cloned_brain.do_thinking();
            }
        });
    }

    // just spawn 4 async executions ...
    brain.think_on(main_thought(10, 10));
    brain.think_on(main_thought(20, 5));
    brain.think_on(main_thought(30, 6));
    brain.think_on(main_thought(40, 3));

    // use the current thread as the 4th core
    loop {
        brain.do_thinking();

        // we could also spawn new  [Thought]s here or from within an
        // async fn to keep the Brain busy ..
    }
}

To be able to look behind the scenes a bit we adjust the implementation of our Future that will hand out a number after a certain amount of poll's to see which core/thread the current poll is executed.

struct GiveNumberFuture {
    number_to_give: u32,
    give_after_tries: u32,
    current_tries: u32,
}

impl Future for GiveNumberFuture {
    type Output = u32;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.get_mut();
        println!("polled {} time(s) - now on {:?}",
            this.current_tries + 1,
            std::thread::current().id()
        );
        if this.give_after_tries > this.current_tries + 1 {
            this.current_tries += 1;
            cx.waker().wake_by_ref();
            Poll::Pending
        } else {
            Poll::Ready(this.number_to_give)
        }
    }
}

async fn main_thought(number: u32, tries: u32) {
    let future = GiveNumberFuture {
        number_to_give: number,
        give_after_tries: tries,
        current_tries: 0,
    };

    let number = future.await;
    println!("waited for {}", number);
}

Running now this example will yield the follwoing output

Hello, world!
polled 1 time(s) - now on ThreadId(1)
polled 1 time(s) - now on ThreadId(1)
polled 2 time(s) - now on ThreadId(1)
polled 3 time(s) - now on ThreadId(1)
waited for 40
polled 1 time(s) - now on ThreadId(3)
polled 2 time(s) - now on ThreadId(1)
polled 3 time(s) - now on ThreadId(3)
polled 4 time(s) - now on ThreadId(1)
polled 5 time(s) - now on ThreadId(3)
polled 1 time(s) - now on ThreadId(2)
polled 2 time(s) - now on ThreadId(3)
polled 3 time(s) - now on ThreadId(2)
polled 4 time(s) - now on ThreadId(3)
polled 5 time(s) - now on ThreadId(2)
waited for 20
polled 6 time(s) - now on ThreadId(1)
waited for 30
polled 2 time(s) - now on ThreadId(4)
polled 3 time(s) - now on ThreadId(3)
polled 4 time(s) - now on ThreadId(2)
polled 5 time(s) - now on ThreadId(1)
polled 6 time(s) - now on ThreadId(3)
polled 7 time(s) - now on ThreadId(2)
polled 8 time(s) - now on ThreadId(1)
polled 9 time(s) - now on ThreadId(2)
polled 10 time(s) - now on ThreadId(3)
waited for 10

You can clearely see how the different cores/threads are picking up the work to drive a waiting Future to completion.