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.