diff --git a/README.md b/README.md index 224e6d4..1550ef3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ The rendered version is found at: [https://cfsamson.github.io/books-futures-explained/](https://cfsamson.github.io/books-futures-explained/) +You can find the main example in the repository [examples-futures](https://github.com/cfsamson/examples-futures). + This book aims to explain `Futures` in Rust using an example driven approach, exploring why they're designed the way they are, and how they work. We'll also take a look at some of the alternatives we have when dealing with concurrency diff --git a/src/6_future_example.md b/src/6_future_example.md index 573077a..4cd804a 100644 --- a/src/6_future_example.md +++ b/src/6_future_example.md @@ -419,11 +419,6 @@ impl Reactor { self.dispatcher.send(Event::Timeout(duration, id)).unwrap(); } - // We send a close event to the reactor so it closes down our reactor-thread - fn close(&mut self) { - self.dispatcher.send(Event::Close).unwrap(); - } - // We simply checks if a task with this id is in the state `TaskState::Ready` fn is_ready(&self, id: usize) -> bool { self.tasks.get(&id).map(|state| match state { @@ -435,6 +430,9 @@ impl Reactor { impl Drop for Reactor { fn drop(&mut self) { + // We send a close event to the reactor so it closes down our reactor-thread. + // If we don't do that we'll en up waiting forever for new events. + self.dispatcher.send(Event::Close).unwrap(); self.handle.take().map(|h| h.join().unwrap()).unwrap(); } } @@ -492,10 +490,6 @@ fn main() { // This executor will block the main thread until the futures is resolved block_on(mainfut); - - // When we're done, we want to shut down our reactor thread so our program - // ends nicely. - reactor.lock().map(|mut r| r.close()).unwrap(); } # // ============================= EXECUTOR ==================================== # fn block_on(mut future: F) -> F::Output { @@ -649,10 +643,6 @@ fn main() { # } # self.dispatcher.send(Event::Timeout(duration, id)).unwrap(); # } -# -# fn close(&mut self) { -# self.dispatcher.send(Event::Close).unwrap(); -# } # # fn is_ready(&self, id: usize) -> bool { # self.tasks.get(&id).map(|state| match state { @@ -664,6 +654,7 @@ fn main() { # # impl Drop for Reactor { # fn drop(&mut self) { +# self.dispatcher.send(Event::Close).unwrap(); # self.handle.take().map(|h| h.join().unwrap()).unwrap(); # } # } @@ -676,6 +667,18 @@ I added a some debug printouts so we can observe a couple of things: The last point is relevant when we move on the the last paragraph. +> There is one subtle thing to note about our example. What happens if we pass +> in the same `id` for both events? +> +> ```rust, ignore +> let future1 = Task::new(reactor.clone(), 1, 1); +> let future2 = Task::new(reactor.clone(), 2, 1); +> ``` +> +> We'll discuss this a bit more under exercises in the last chapter where we +> also look at ways to fix it. For now, just make a note of it so you're aware +> of the problem. + ## Async/Await and concurrecy The `async` keyword can be used on functions as in `async fn(...)` or on a diff --git a/src/8_finished_example.md b/src/8_finished_example.md index 5ed071d..d21f959 100644 --- a/src/8_finished_example.md +++ b/src/8_finished_example.md @@ -25,7 +25,6 @@ fn main() { }; block_on(mainfut); - reactor.lock().map(|mut r| r.close()).unwrap(); } use std::{ @@ -72,7 +71,6 @@ fn block_on(mut future: F) -> F::Output { struct MyWaker { parker: Arc, } - #[derive(Clone)] pub struct Task { id: usize, @@ -193,10 +191,6 @@ impl Reactor { self.dispatcher.send(Event::Timeout(duration, id)).unwrap(); } - fn close(&mut self) { - self.dispatcher.send(Event::Close).unwrap(); - } - fn is_ready(&self, id: usize) -> bool { self.tasks.get(&id).map(|state| match state { TaskState::Ready => true, @@ -207,6 +201,7 @@ impl Reactor { impl Drop for Reactor { fn drop(&mut self) { + self.dispatcher.send(Event::Close).unwrap(); self.handle.take().map(|h| h.join().unwrap()).unwrap(); } } diff --git a/src/conclusion.md b/src/conclusion.md index ceaafee..2de9c60 100644 --- a/src/conclusion.md +++ b/src/conclusion.md @@ -39,6 +39,28 @@ can run multiple futures concurrently. As I suggested in the start of this book, visiting [@stjepan'sblog series about implementing your own executors](https://stjepang.github.io/2020/01/31/build-your-own-executor.html) is the place I would start and take it from there. +### Create an unique Id for each task + +As we discussed in the end of the main example. What happens if the user pass in +the same Id for both events? + +```rust, ignore +let future1 = Task::new(reactor.clone(), 1, 1); +let future2 = Task::new(reactor.clone(), 2, 1); +``` + +Right now it will deadlock since our `poll` method thinks the first poll of +`future2` is `future1` getting polled again and swaps out the waker with the +one from `future2`. This waker never gets called since the task is never +registered. + +It's probably a bad idea to expose the user to this behavior, so we +should have a unique Id for each task which we use internally. There are a +many ways to solve this. Below is two suggestions to get going: + +1. Let the reactor have a `usize` which is incremented on every task creation +2. Use a crate like `Guid` to generate an unique Id for each task + ## Further reading There are many great resources. In addition to the RFCs and articles I've already