Removed the explicit call to close and mentioned a bug that can occur
when two tasks is given the same id. The explicit close call is not needed. We can do that in the `Drop` implementation instead. It's better to have accounting tasks like this only one place and it didn't add anything to the example. There is a subtle bug which occurs if two tasks are given the same Id. I mentioned this explicitly since it's such an easy thing to do. I added the fix as a reader excercise since for the examples sake I think it's better to pass them in explicitly so that we don't "pollute" the example with more code than strictly needed to get an understanding of futures.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<F: Future>(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
|
||||
|
||||
@@ -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<F: Future>(mut future: F) -> F::Output {
|
||||
struct MyWaker {
|
||||
parker: Arc<Parker>,
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user