From df5613c87e8a77283b51bade2604465e390173a2 Mon Sep 17 00:00:00 2001 From: Carl Fredrik Samson Date: Wed, 7 Dec 2022 16:48:48 +0100 Subject: [PATCH] clearified and added more info in response to #50 --- .vscode/settings.json | 7 +++++-- src/1_futures_in_rust.md | 17 ++++++----------- src/3_waker_context.md | 7 +++++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ceb5c5..7baadaf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,11 @@ { - "spellright.language": [], + "spellright.language": [ + "en" + ], "spellright.documentTypes": [ "latex", - "plaintext" + "plaintext", + "markdown" ], "cSpell.enabled": true } \ No newline at end of file diff --git a/src/1_futures_in_rust.md b/src/1_futures_in_rust.md index 11c9216..4adbaf8 100644 --- a/src/1_futures_in_rust.md +++ b/src/1_futures_in_rust.md @@ -122,25 +122,20 @@ The `Waker` is how the reactor tells the executor that a specific Future is read understand the life cycle and ownership of a Waker, you'll understand how futures work from a user's perspective. Here is the life cycle: -- A Waker is created by the **executor.** A common, but not required, method is - to create a new Waker for each Future that is registered with the executor. -- When a future is registered with an executor, it’s given a clone of the Waker +- A Waker is created by the **executor.** +- When a future is polled the first time by the executor, it’s given a clone of the Waker object created by the executor. Since this is a shared object (e.g. an `Arc`), all clones actually point to the same underlying object. Thus, anything that calls _any_ clone of the original Waker will wake the particular Future that was registered to it. - The future clones the Waker and passes it to the reactor, which stores it to use later. + +You could think of a "future" like a channel for the `Waker`: The channel starts with the future that's polled the first time by the executor and is passed a handle to a `Waker`. It ends in a leaf-future which passes that handle to the reactor. -At some point in the future, the reactor will decide that the future is ready to run. It will wake -the future via the Waker that it stored. This action will do what is necessary to get the executor -in a position to poll the future. +>Note that the `Waker` is wrapped in a rather uninteresting `Context` struct which we will learn more about later. The interesting part is the `Waker` that is passed on. -The Waker object implements everything that we associate with -[task](https://doc.rust-lang.org/std/task/index.html). The object is specific to the type of -executor in use, but all Wakers share a similar interface (it's not a trait because -embedded systems can't handle trait objects, but a useful abstraction is to think of it as a trait -object). +At some point in the future, the reactor will decide that the future is ready to run. It will wake the future via the Waker that it stored. This action will do what is necessary to get the executor in a position to poll the future. We'll go into more detail on Wakers in the [Waker and Context chapter.](3_waker_context.md#understanding-the-waker) Since the interface is the same across all executors, reactors can _in theory_ be completely oblivious to the type of the executor, and vice-versa. **Executors and reactors never need to diff --git a/src/3_waker_context.md b/src/3_waker_context.md index 717d323..da175fc 100644 --- a/src/3_waker_context.md +++ b/src/3_waker_context.md @@ -27,8 +27,7 @@ extend the ecosystem with new leaf-level tasks. ## The Context type As the docs state as of now this type only wraps a `Waker`, but it gives some -flexibility for future evolutions of the API in Rust. The context can for example hold -task-local storage and provide space for debugging hooks in later iterations. +flexibility for future evolutions of the API in Rust. The context can for example hold task-local storage and provide space for debugging hooks in later iterations. ## Understanding the `Waker` @@ -37,6 +36,10 @@ is how we implement a `Waker` . Creating a `Waker` involves creating a `vtable` which allows us to use dynamic dispatch to call methods on a _type erased_ trait object we construct ourselves. +The `Waker` implementation is specific to the type of executor in use, but all Wakers share a similar interface. It's useful to think of it as a `Trait`. It's not implemented as such since that would require us to treat it like a trait object which _forces_ dynamic dispatch for all users (which in turn degrades performance when it's not needed). In addition, even if we accepted the performance cost forced on all users, it either restricts the API severely by requiring a `&dyn Waker` like trait object, or would require an `Arc` which in turn requires a heap allocation which a lot of embedded-like systems can't do. + +Having the Waker implemented the way it is supports users creating a statically-allocated wakers and even more exotic mechanisms to on platforms where that makes sense. + >If you want to know more about dynamic dispatch in Rust I can recommend an article written by Adam Schwalm called [Exploring Dynamic Dispatch in Rust](https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/).