Added Bonus Section implementing a proper Parker
The problems addressed in the earlier version led to an "incorrect" example which is bad to pass along after reading a whole book. after getting some feedback in #2 i decided to show how we can create a proper `Parker`. The main example (which I assume most interested readers will copy) now uses a proper parking thechnique so there should be no more dataraces left. I also removed the "Reader Excercise" paragraph suggesting that they explore a way to implement proper parking since we now show that in our main example.
This commit is contained in:
@@ -47,6 +47,11 @@ Feedback, questions or discussion is welcome in the issue tracker.
|
|||||||
readability and make it easier to reason about. In addition I fixed a mistake
|
readability and make it easier to reason about. In addition I fixed a mistake
|
||||||
in the `Poll` method and a possible race condition. See #2 for more details.
|
in the `Poll` method and a possible race condition. See #2 for more details.
|
||||||
|
|
||||||
|
**2020-04-13:** Added a "bonus section" to the [Implementing Futures chapter](https://cfsamson.github.io/books-futures-explained/6_future_example.html) where we avoid using `thread::park` and instead show how we
|
||||||
|
can use a `Condvar` and a `Mutex` to create a proper `Parker`. Updated the [Finished Example](https://cfsamson.github.io/books-futures-explained/8_finished_example.html) to reflect these changes. Unfortunately, this led us
|
||||||
|
a few lines over my initial promis of keeping the example below 200 LOC but the I think the inclusion
|
||||||
|
is worth it.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This book is MIT licensed.
|
This book is MIT licensed.
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ here will be in <code>main.rs</code></p>
|
|||||||
</ul>
|
</ul>
|
||||||
<p>Rust provides a way for the Reactor and Executor to communicate through the <code>Waker</code>. The reactor stores this <code>Waker</code> and calls <code>Waker::wake()</code> on it once
|
<p>Rust provides a way for the Reactor and Executor to communicate through the <code>Waker</code>. The reactor stores this <code>Waker</code> and calls <code>Waker::wake()</code> on it once
|
||||||
a <code>Future</code> has resolved and should be polled again.</p>
|
a <code>Future</code> has resolved and should be polled again.</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>Notice that this chapter has a bonus section called <a href="./6_future_example.html#bonus-section---a-proper-way-to-park-our-thread">A Proper Way to Park our Thread</a> which shows how to avoid <code>thread::park</code>.</p>
|
||||||
|
</blockquote>
|
||||||
<p><strong>Our Executor will look like this:</strong></p>
|
<p><strong>Our Executor will look like this:</strong></p>
|
||||||
<pre><code class="language-rust noplaypen ignore">// Our executor takes any object which implements the `Future` trait
|
<pre><code class="language-rust noplaypen ignore">// Our executor takes any object which implements the `Future` trait
|
||||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
@@ -210,10 +213,9 @@ fn block_on<F: Future>(mut future: F) -> F::Output {
|
|||||||
// We poll in a loop, but it's not a busy loop. It will only run when
|
// We poll in a loop, but it's not a busy loop. It will only run when
|
||||||
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
|
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
|
||||||
// that can happen for no good reason).
|
// that can happen for no good reason).
|
||||||
let val = loop {
|
let val = loop {
|
||||||
|
match Future::poll(future, &mut cx) {
|
||||||
match Future::poll(pinned, &mut cx) {
|
|
||||||
|
|
||||||
// when the Future is ready we're finished
|
// when the Future is ready we're finished
|
||||||
Poll::Ready(val) => break val,
|
Poll::Ready(val) => break val,
|
||||||
|
|
||||||
@@ -227,6 +229,12 @@ fn block_on<F: Future>(mut future: F) -> F::Output {
|
|||||||
<p>In all the examples you'll see in this chapter I've chosen to comment the code
|
<p>In all the examples you'll see in this chapter I've chosen to comment the code
|
||||||
extensively. I find it easier to follow along that way so I'll not repeat myself
|
extensively. I find it easier to follow along that way so I'll not repeat myself
|
||||||
here and focus only on some important aspects that might need further explanation.</p>
|
here and focus only on some important aspects that might need further explanation.</p>
|
||||||
|
<p>It's worth noting that simply calling <code>thread::sleep</code> as we do here can lead to
|
||||||
|
both deadlocks and errors. We'll explain a bit more later and fix this if you
|
||||||
|
read all the way to the <a href="./6_future_example.html##bonus-section---a-proper-way-to-park-our-thread">Bonus Section</a> at
|
||||||
|
the end of this chapter.</p>
|
||||||
|
<p>For now, we keep it as simple and easy to understand as we can by just going
|
||||||
|
to sleep.</p>
|
||||||
<p>Now that you've read so much about <code>Generator</code>s and <code>Pin</code> already this should
|
<p>Now that you've read so much about <code>Generator</code>s and <code>Pin</code> already this should
|
||||||
be rather easy to understand. <code>Future</code> is a state machine, every <code>await</code> point
|
be rather easy to understand. <code>Future</code> is a state machine, every <code>await</code> point
|
||||||
is a <code>yield</code> point. We could borrow data across <code>await</code> points and we meet the
|
is a <code>yield</code> point. We could borrow data across <code>await</code> points and we meet the
|
||||||
@@ -381,29 +389,10 @@ without passing around a reference.</p>
|
|||||||
<blockquote>
|
<blockquote>
|
||||||
<h3><a class="header" href="#why-using-thread-parkunpark-is-a-bad-idea-for-a-library" id="why-using-thread-parkunpark-is-a-bad-idea-for-a-library">Why using thread park/unpark is a bad idea for a library</a></h3>
|
<h3><a class="header" href="#why-using-thread-parkunpark-is-a-bad-idea-for-a-library" id="why-using-thread-parkunpark-is-a-bad-idea-for-a-library">Why using thread park/unpark is a bad idea for a library</a></h3>
|
||||||
<p>It could deadlock easily since anyone could get a handle to the <code>executor thread</code>
|
<p>It could deadlock easily since anyone could get a handle to the <code>executor thread</code>
|
||||||
and call park/unpark on it.</p>
|
and call park/unpark on our thread or we could have a race condition where the
|
||||||
<ol>
|
future resolves and calls <code>wake</code> before we have time to go to sleep in our
|
||||||
<li>A future could call <code>unpark</code> on the executor thread from a different thread</li>
|
executor. We'll se how we can fix this at the end of this chapter.</p>
|
||||||
<li>Our <code>executor</code> thinks that data is ready and wakes up and polls the future</li>
|
|
||||||
<li>The future is not ready yet when polled, but at that exact same time the
|
|
||||||
<code>Reactor</code> gets an event and calls <code>wake()</code> which also unparks our thread.</li>
|
|
||||||
<li>This could happen before we go to sleep again since these processes
|
|
||||||
run in parallel.</li>
|
|
||||||
<li>Our reactor has called <code>wake</code> but our thread is still sleeping since it was
|
|
||||||
awake already at that point.</li>
|
|
||||||
<li>We're deadlocked and our program stops working</li>
|
|
||||||
</ol>
|
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<blockquote>
|
|
||||||
<p>There is also the case that our thread could have what's called a
|
|
||||||
<code>spurious wakeup</code> (<a href="https://cfsamson.github.io/book-exploring-async-basics/9_3_http_module.html#bonus-section">which can happen unexpectedly</a>), which
|
|
||||||
could cause the same deadlock if we're unlucky.</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>There are several better solutions, here are some:</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html">std::sync::CondVar</a></li>
|
|
||||||
<li><a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html">crossbeam::sync::Parker</a></li>
|
|
||||||
</ul>
|
|
||||||
<h2><a class="header" href="#the-reactor" id="the-reactor">The Reactor</a></h2>
|
<h2><a class="header" href="#the-reactor" id="the-reactor">The Reactor</a></h2>
|
||||||
<p>This is the home stretch, and not strictly <code>Future</code> related, but we need one
|
<p>This is the home stretch, and not strictly <code>Future</code> related, but we need one
|
||||||
to have an example to run.</p>
|
to have an example to run.</p>
|
||||||
@@ -596,14 +585,12 @@ which you can edit and change the way you like.</p>
|
|||||||
// our code into a state machine, `yielding` at every `await` point.
|
// our code into a state machine, `yielding` at every `await` point.
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = future1.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = future2.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Our executor can only run one and one future, this is pretty normal
|
// Our executor can only run one and one future, this is pretty normal
|
||||||
@@ -837,6 +824,82 @@ for today.</p>
|
|||||||
<p>I hope exploring Futures and async in general gets easier after this read and I
|
<p>I hope exploring Futures and async in general gets easier after this read and I
|
||||||
do really hope that you do continue to explore further.</p>
|
do really hope that you do continue to explore further.</p>
|
||||||
<p>Don't forget the exercises in the last chapter 😊.</p>
|
<p>Don't forget the exercises in the last chapter 😊.</p>
|
||||||
|
<h2><a class="header" href="#bonus-section---a-proper-way-to-park-our-thread" id="bonus-section---a-proper-way-to-park-our-thread">Bonus Section - a Proper Way to Park our Thread</a></h2>
|
||||||
|
<p>As we explained earlier in our chapter, simply calling <code>thread::sleep</code> is not really
|
||||||
|
sufficient to implement a proper reactor. You can also reach a tool like the <code>Parker</code>
|
||||||
|
in crossbeam: <a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html">crossbeam::sync::Parker</a></p>
|
||||||
|
<p>Since it doesn't require many lines of code to create a working solution ourselves we'll show how
|
||||||
|
we can solve that by using a <code>Condvar</code> and a <code>Mutex</code> instead.</p>
|
||||||
|
<p>Start by implementing our own <code>Parker</code> like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">#[derive(Default)]
|
||||||
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
|
|
||||||
|
impl Parker {
|
||||||
|
fn park(&self) {
|
||||||
|
|
||||||
|
// We aquire a lock to the Mutex which protects our flag indicating if we
|
||||||
|
// should resume execution or not.
|
||||||
|
let mut resumable = self.0.lock().unwrap();
|
||||||
|
|
||||||
|
// We put this in a loop since there is a chance we'll get woken, but
|
||||||
|
// our flag hasn't changed. If that happens, we simply go back to sleep.
|
||||||
|
while !*resumable {
|
||||||
|
|
||||||
|
// We sleep until someone notifies us
|
||||||
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We immidiately set the condition to false, so that next time we call `park` we'll
|
||||||
|
// go right to sleep.
|
||||||
|
*resumable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
// We simply acquire a lock to our flag and sets the condition to `runnable` when we
|
||||||
|
// get it.
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
|
||||||
|
// We notify our `Condvar` so it wakes up and resumes.
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>The <code>Condvar</code> in Rust is designed to work together with a Mutex. Usually, you'd think that we don't
|
||||||
|
release the mutex-lock we acquire in <code>self.0.lock().unwrap();</code> before we go to sleep. Which means
|
||||||
|
that our <code>unpark</code> function never will acquire a lock to our flag and we deadlock.</p>
|
||||||
|
<p>Using <code>Condvar</code> we avoid this since the <code>Condvar</code> will consume our lock so it's released at the
|
||||||
|
moment we go to sleep.</p>
|
||||||
|
<p>When we resume again, our <code>Condvar</code> returns our lock so we can continue to operate on it.</p>
|
||||||
|
<p>This means we need to make some very slight changes to our executor like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default()); // <--- NB!
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() }); <--- NB!
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(), // <--- NB!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>And we need to change our <code>Waker</code> like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">#[derive(Clone)]
|
||||||
|
struct MyWaker {
|
||||||
|
parker: Arc<Parker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
|
waker_arc.parker.unpark();
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>And that's really all there is to it. The next chapter shows our finished code with this
|
||||||
|
improvement which you can explore further if you wish.</p>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -158,19 +158,15 @@ run it yourself. Have fun!</p>
|
|||||||
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
|
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let reactor = Reactor::new();
|
let reactor = Reactor::new();
|
||||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
|
||||||
let future2 = Task::new(reactor.clone(), 2, 2);
|
|
||||||
|
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = Task::new(reactor.clone(), 1, 1).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = Task::new(reactor.clone(), 2, 2).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mainfut = async {
|
let mainfut = async {
|
||||||
@@ -181,35 +177,50 @@ run it yourself. Have fun!</p>
|
|||||||
block_on(mainfut);
|
block_on(mainfut);
|
||||||
reactor.lock().map(|mut r| r.close()).unwrap();
|
reactor.lock().map(|mut r| r.close()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
|
future::Future, sync::{ mpsc::{channel, Sender}, Arc, Mutex, Condvar},
|
||||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
|
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem, pin::Pin,
|
||||||
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================= EXECUTOR ====================================
|
// ============================= EXECUTOR ====================================
|
||||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
#[derive(Default)]
|
||||||
let mywaker = Arc::new(MyWaker {
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
thread: thread::current(),
|
|
||||||
});
|
|
||||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
|
||||||
let mut cx = Context::from_waker(&waker);
|
|
||||||
|
|
||||||
// SAFETY: we shadow `future` so it can't be accessed again.
|
impl Parker {
|
||||||
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
fn park(&self) {
|
||||||
let val = loop {
|
let mut resumable = self.0.lock().unwrap();
|
||||||
match Future::poll(future.as_mut(), &mut cx) {
|
while !*resumable {
|
||||||
Poll::Ready(val) => break val,
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
Poll::Pending => thread::park(),
|
}
|
||||||
};
|
*resumable = false;
|
||||||
};
|
}
|
||||||
val
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default());
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() });
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MyWaker {
|
struct MyWaker {
|
||||||
thread: thread::Thread,
|
parker: Arc<Parker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -220,9 +231,8 @@ pub struct Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_wake(s: &MyWaker) {
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
let waker_ptr: *const MyWaker = s;
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
|
waker_arc.parker.unpark();
|
||||||
waker_arc.thread.unpark();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||||
@@ -240,7 +250,7 @@ const VTABLE: RawWakerVTable = unsafe {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
fn waker_into_waker(s: *const MyWaker) -> Waker {
|
fn mywaker_into_waker(s: *const MyWaker) -> Waker {
|
||||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||||
unsafe { Waker::from_raw(raw_waker) }
|
unsafe { Waker::from_raw(raw_waker) }
|
||||||
}
|
}
|
||||||
@@ -256,21 +266,17 @@ impl Future for Task {
|
|||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let mut r = self.reactor.lock().unwrap();
|
let mut r = self.reactor.lock().unwrap();
|
||||||
if r.is_ready(self.id) {
|
if r.is_ready(self.id) {
|
||||||
println!("POLL: TASK {} IS READY", self.id);
|
|
||||||
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
||||||
Poll::Ready(self.id)
|
Poll::Ready(self.id)
|
||||||
} else if r.tasks.contains_key(&self.id) {
|
} else if r.tasks.contains_key(&self.id) {
|
||||||
println!("POLL: REPLACED WAKER FOR TASK: {}", self.id);
|
|
||||||
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
println!("POLL: REGISTERED TASK: {}, WAKER: {:?}", self.id, cx.waker());
|
|
||||||
r.register(self.data, cx.waker().clone(), self.id);
|
r.register(self.data, cx.waker().clone(), self.id);
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================== REACTOR ===================================
|
// =============================== REACTOR ===================================
|
||||||
enum TaskState {
|
enum TaskState {
|
||||||
Ready,
|
Ready,
|
||||||
@@ -322,13 +328,12 @@ impl Reactor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn wake(&mut self, id: usize) {
|
fn wake(&mut self, id: usize) {
|
||||||
self.tasks.get_mut(&id).map(|state| {
|
let state = self.tasks.get_mut(&id).unwrap();
|
||||||
match mem::replace(state, TaskState::Ready) {
|
match mem::replace(state, TaskState::Ready) {
|
||||||
TaskState::NotReady(waker) => waker.wake(),
|
TaskState::NotReady(waker) => waker.wake(),
|
||||||
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
||||||
|
|||||||
@@ -164,15 +164,6 @@ I'll try my best to respond to each one of them.</p>
|
|||||||
<p>So our implementation has taken some obvious shortcuts and could use some improvement.
|
<p>So our implementation has taken some obvious shortcuts and could use some improvement.
|
||||||
Actually digging into the code and try things yourself is a good way to learn. Here are
|
Actually digging into the code and try things yourself is a good way to learn. Here are
|
||||||
some good exercises if you want to explore more:</p>
|
some good exercises if you want to explore more:</p>
|
||||||
<h3><a class="header" href="#avoid-threadpark" id="avoid-threadpark">Avoid <code>thread::park</code></a></h3>
|
|
||||||
<p>The big problem using <code>Thread::park</code> and <code>Thread::unpark</code> is that the user can access these
|
|
||||||
same methods from their own code. Try to use another method to suspend our thread and wake
|
|
||||||
it up again on our command. Some hints:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Check out <code>CondVars</code>, here are two sources <a href="https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables">Wikipedia</a> and the
|
|
||||||
docs for <a href="https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html"><code>CondVar</code></a></li>
|
|
||||||
<li>Take a look at crates that help you with this exact problem like <a href="https://github.com/crossbeam-rs/crossbeam">Crossbeam </a>(specifically the <a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html"><code>Parker</code></a>)</li>
|
|
||||||
</ul>
|
|
||||||
<h3><a class="header" href="#avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around" id="avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around">Avoid wrapping the whole <code>Reactor</code> in a mutex and pass it around</a></h3>
|
<h3><a class="header" href="#avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around" id="avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around">Avoid wrapping the whole <code>Reactor</code> in a mutex and pass it around</a></h3>
|
||||||
<p>First of all, protecting the whole <code>Reactor</code> and passing it around is overkill. We're only
|
<p>First of all, protecting the whole <code>Reactor</code> and passing it around is overkill. We're only
|
||||||
interested in synchronizing some parts of the information it contains. Try to refactor that
|
interested in synchronizing some parts of the information it contains. Try to refactor that
|
||||||
|
|||||||
221
book/print.html
221
book/print.html
@@ -2343,6 +2343,9 @@ here will be in <code>main.rs</code></p>
|
|||||||
</ul>
|
</ul>
|
||||||
<p>Rust provides a way for the Reactor and Executor to communicate through the <code>Waker</code>. The reactor stores this <code>Waker</code> and calls <code>Waker::wake()</code> on it once
|
<p>Rust provides a way for the Reactor and Executor to communicate through the <code>Waker</code>. The reactor stores this <code>Waker</code> and calls <code>Waker::wake()</code> on it once
|
||||||
a <code>Future</code> has resolved and should be polled again.</p>
|
a <code>Future</code> has resolved and should be polled again.</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>Notice that this chapter has a bonus section called <a href="./6_future_example.html#bonus-section---a-proper-way-to-park-our-thread">A Proper Way to Park our Thread</a> which shows how to avoid <code>thread::park</code>.</p>
|
||||||
|
</blockquote>
|
||||||
<p><strong>Our Executor will look like this:</strong></p>
|
<p><strong>Our Executor will look like this:</strong></p>
|
||||||
<pre><code class="language-rust noplaypen ignore">// Our executor takes any object which implements the `Future` trait
|
<pre><code class="language-rust noplaypen ignore">// Our executor takes any object which implements the `Future` trait
|
||||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
@@ -2366,10 +2369,9 @@ fn block_on<F: Future>(mut future: F) -> F::Output {
|
|||||||
// We poll in a loop, but it's not a busy loop. It will only run when
|
// We poll in a loop, but it's not a busy loop. It will only run when
|
||||||
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
|
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
|
||||||
// that can happen for no good reason).
|
// that can happen for no good reason).
|
||||||
let val = loop {
|
let val = loop {
|
||||||
|
match Future::poll(future, &mut cx) {
|
||||||
match Future::poll(pinned, &mut cx) {
|
|
||||||
|
|
||||||
// when the Future is ready we're finished
|
// when the Future is ready we're finished
|
||||||
Poll::Ready(val) => break val,
|
Poll::Ready(val) => break val,
|
||||||
|
|
||||||
@@ -2383,6 +2385,12 @@ fn block_on<F: Future>(mut future: F) -> F::Output {
|
|||||||
<p>In all the examples you'll see in this chapter I've chosen to comment the code
|
<p>In all the examples you'll see in this chapter I've chosen to comment the code
|
||||||
extensively. I find it easier to follow along that way so I'll not repeat myself
|
extensively. I find it easier to follow along that way so I'll not repeat myself
|
||||||
here and focus only on some important aspects that might need further explanation.</p>
|
here and focus only on some important aspects that might need further explanation.</p>
|
||||||
|
<p>It's worth noting that simply calling <code>thread::sleep</code> as we do here can lead to
|
||||||
|
both deadlocks and errors. We'll explain a bit more later and fix this if you
|
||||||
|
read all the way to the <a href="./6_future_example.html##bonus-section---a-proper-way-to-park-our-thread">Bonus Section</a> at
|
||||||
|
the end of this chapter.</p>
|
||||||
|
<p>For now, we keep it as simple and easy to understand as we can by just going
|
||||||
|
to sleep.</p>
|
||||||
<p>Now that you've read so much about <code>Generator</code>s and <code>Pin</code> already this should
|
<p>Now that you've read so much about <code>Generator</code>s and <code>Pin</code> already this should
|
||||||
be rather easy to understand. <code>Future</code> is a state machine, every <code>await</code> point
|
be rather easy to understand. <code>Future</code> is a state machine, every <code>await</code> point
|
||||||
is a <code>yield</code> point. We could borrow data across <code>await</code> points and we meet the
|
is a <code>yield</code> point. We could borrow data across <code>await</code> points and we meet the
|
||||||
@@ -2537,29 +2545,10 @@ without passing around a reference.</p>
|
|||||||
<blockquote>
|
<blockquote>
|
||||||
<h3><a class="header" href="#why-using-thread-parkunpark-is-a-bad-idea-for-a-library" id="why-using-thread-parkunpark-is-a-bad-idea-for-a-library">Why using thread park/unpark is a bad idea for a library</a></h3>
|
<h3><a class="header" href="#why-using-thread-parkunpark-is-a-bad-idea-for-a-library" id="why-using-thread-parkunpark-is-a-bad-idea-for-a-library">Why using thread park/unpark is a bad idea for a library</a></h3>
|
||||||
<p>It could deadlock easily since anyone could get a handle to the <code>executor thread</code>
|
<p>It could deadlock easily since anyone could get a handle to the <code>executor thread</code>
|
||||||
and call park/unpark on it.</p>
|
and call park/unpark on our thread or we could have a race condition where the
|
||||||
<ol>
|
future resolves and calls <code>wake</code> before we have time to go to sleep in our
|
||||||
<li>A future could call <code>unpark</code> on the executor thread from a different thread</li>
|
executor. We'll se how we can fix this at the end of this chapter.</p>
|
||||||
<li>Our <code>executor</code> thinks that data is ready and wakes up and polls the future</li>
|
|
||||||
<li>The future is not ready yet when polled, but at that exact same time the
|
|
||||||
<code>Reactor</code> gets an event and calls <code>wake()</code> which also unparks our thread.</li>
|
|
||||||
<li>This could happen before we go to sleep again since these processes
|
|
||||||
run in parallel.</li>
|
|
||||||
<li>Our reactor has called <code>wake</code> but our thread is still sleeping since it was
|
|
||||||
awake already at that point.</li>
|
|
||||||
<li>We're deadlocked and our program stops working</li>
|
|
||||||
</ol>
|
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<blockquote>
|
|
||||||
<p>There is also the case that our thread could have what's called a
|
|
||||||
<code>spurious wakeup</code> (<a href="https://cfsamson.github.io/book-exploring-async-basics/9_3_http_module.html#bonus-section">which can happen unexpectedly</a>), which
|
|
||||||
could cause the same deadlock if we're unlucky.</p>
|
|
||||||
</blockquote>
|
|
||||||
<p>There are several better solutions, here are some:</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html">std::sync::CondVar</a></li>
|
|
||||||
<li><a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html">crossbeam::sync::Parker</a></li>
|
|
||||||
</ul>
|
|
||||||
<h2><a class="header" href="#the-reactor" id="the-reactor">The Reactor</a></h2>
|
<h2><a class="header" href="#the-reactor" id="the-reactor">The Reactor</a></h2>
|
||||||
<p>This is the home stretch, and not strictly <code>Future</code> related, but we need one
|
<p>This is the home stretch, and not strictly <code>Future</code> related, but we need one
|
||||||
to have an example to run.</p>
|
to have an example to run.</p>
|
||||||
@@ -2752,14 +2741,12 @@ which you can edit and change the way you like.</p>
|
|||||||
// our code into a state machine, `yielding` at every `await` point.
|
// our code into a state machine, `yielding` at every `await` point.
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = future1.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = future2.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Our executor can only run one and one future, this is pretty normal
|
// Our executor can only run one and one future, this is pretty normal
|
||||||
@@ -2993,25 +2980,97 @@ for today.</p>
|
|||||||
<p>I hope exploring Futures and async in general gets easier after this read and I
|
<p>I hope exploring Futures and async in general gets easier after this read and I
|
||||||
do really hope that you do continue to explore further.</p>
|
do really hope that you do continue to explore further.</p>
|
||||||
<p>Don't forget the exercises in the last chapter 😊.</p>
|
<p>Don't forget the exercises in the last chapter 😊.</p>
|
||||||
|
<h2><a class="header" href="#bonus-section---a-proper-way-to-park-our-thread" id="bonus-section---a-proper-way-to-park-our-thread">Bonus Section - a Proper Way to Park our Thread</a></h2>
|
||||||
|
<p>As we explained earlier in our chapter, simply calling <code>thread::sleep</code> is not really
|
||||||
|
sufficient to implement a proper reactor. You can also reach a tool like the <code>Parker</code>
|
||||||
|
in crossbeam: <a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html">crossbeam::sync::Parker</a></p>
|
||||||
|
<p>Since it doesn't require many lines of code to create a working solution ourselves we'll show how
|
||||||
|
we can solve that by using a <code>Condvar</code> and a <code>Mutex</code> instead.</p>
|
||||||
|
<p>Start by implementing our own <code>Parker</code> like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">#[derive(Default)]
|
||||||
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
|
|
||||||
|
impl Parker {
|
||||||
|
fn park(&self) {
|
||||||
|
|
||||||
|
// We aquire a lock to the Mutex which protects our flag indicating if we
|
||||||
|
// should resume execution or not.
|
||||||
|
let mut resumable = self.0.lock().unwrap();
|
||||||
|
|
||||||
|
// We put this in a loop since there is a chance we'll get woken, but
|
||||||
|
// our flag hasn't changed. If that happens, we simply go back to sleep.
|
||||||
|
while !*resumable {
|
||||||
|
|
||||||
|
// We sleep until someone notifies us
|
||||||
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We immidiately set the condition to false, so that next time we call `park` we'll
|
||||||
|
// go right to sleep.
|
||||||
|
*resumable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
// We simply acquire a lock to our flag and sets the condition to `runnable` when we
|
||||||
|
// get it.
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
|
||||||
|
// We notify our `Condvar` so it wakes up and resumes.
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>The <code>Condvar</code> in Rust is designed to work together with a Mutex. Usually, you'd think that we don't
|
||||||
|
release the mutex-lock we acquire in <code>self.0.lock().unwrap();</code> before we go to sleep. Which means
|
||||||
|
that our <code>unpark</code> function never will acquire a lock to our flag and we deadlock.</p>
|
||||||
|
<p>Using <code>Condvar</code> we avoid this since the <code>Condvar</code> will consume our lock so it's released at the
|
||||||
|
moment we go to sleep.</p>
|
||||||
|
<p>When we resume again, our <code>Condvar</code> returns our lock so we can continue to operate on it.</p>
|
||||||
|
<p>This means we need to make some very slight changes to our executor like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default()); // <--- NB!
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() }); <--- NB!
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(), // <--- NB!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>And we need to change our <code>Waker</code> like this:</p>
|
||||||
|
<pre><code class="language-rust ignore">#[derive(Clone)]
|
||||||
|
struct MyWaker {
|
||||||
|
parker: Arc<Parker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
|
waker_arc.parker.unpark();
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>And that's really all there is to it. The next chapter shows our finished code with this
|
||||||
|
improvement which you can explore further if you wish.</p>
|
||||||
<h1><a class="header" href="#our-finished-code" id="our-finished-code">Our finished code</a></h1>
|
<h1><a class="header" href="#our-finished-code" id="our-finished-code">Our finished code</a></h1>
|
||||||
<p>Here is the whole example. You can edit it right here in your browser and
|
<p>Here is the whole example. You can edit it right here in your browser and
|
||||||
run it yourself. Have fun!</p>
|
run it yourself. Have fun!</p>
|
||||||
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
|
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let reactor = Reactor::new();
|
let reactor = Reactor::new();
|
||||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
|
||||||
let future2 = Task::new(reactor.clone(), 2, 2);
|
|
||||||
|
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = Task::new(reactor.clone(), 1, 1).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = Task::new(reactor.clone(), 2, 2).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mainfut = async {
|
let mainfut = async {
|
||||||
@@ -3022,35 +3081,50 @@ run it yourself. Have fun!</p>
|
|||||||
block_on(mainfut);
|
block_on(mainfut);
|
||||||
reactor.lock().map(|mut r| r.close()).unwrap();
|
reactor.lock().map(|mut r| r.close()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
|
future::Future, sync::{ mpsc::{channel, Sender}, Arc, Mutex, Condvar},
|
||||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
|
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem, pin::Pin,
|
||||||
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================= EXECUTOR ====================================
|
// ============================= EXECUTOR ====================================
|
||||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
#[derive(Default)]
|
||||||
let mywaker = Arc::new(MyWaker {
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
thread: thread::current(),
|
|
||||||
});
|
|
||||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
|
||||||
let mut cx = Context::from_waker(&waker);
|
|
||||||
|
|
||||||
// SAFETY: we shadow `future` so it can't be accessed again.
|
impl Parker {
|
||||||
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
fn park(&self) {
|
||||||
let val = loop {
|
let mut resumable = self.0.lock().unwrap();
|
||||||
match Future::poll(future.as_mut(), &mut cx) {
|
while !*resumable {
|
||||||
Poll::Ready(val) => break val,
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
Poll::Pending => thread::park(),
|
}
|
||||||
};
|
*resumable = false;
|
||||||
};
|
}
|
||||||
val
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default());
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() });
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MyWaker {
|
struct MyWaker {
|
||||||
thread: thread::Thread,
|
parker: Arc<Parker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -3061,9 +3135,8 @@ pub struct Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_wake(s: &MyWaker) {
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
let waker_ptr: *const MyWaker = s;
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
|
waker_arc.parker.unpark();
|
||||||
waker_arc.thread.unpark();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||||
@@ -3081,7 +3154,7 @@ const VTABLE: RawWakerVTable = unsafe {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
fn waker_into_waker(s: *const MyWaker) -> Waker {
|
fn mywaker_into_waker(s: *const MyWaker) -> Waker {
|
||||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||||
unsafe { Waker::from_raw(raw_waker) }
|
unsafe { Waker::from_raw(raw_waker) }
|
||||||
}
|
}
|
||||||
@@ -3097,21 +3170,17 @@ impl Future for Task {
|
|||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let mut r = self.reactor.lock().unwrap();
|
let mut r = self.reactor.lock().unwrap();
|
||||||
if r.is_ready(self.id) {
|
if r.is_ready(self.id) {
|
||||||
println!("POLL: TASK {} IS READY", self.id);
|
|
||||||
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
||||||
Poll::Ready(self.id)
|
Poll::Ready(self.id)
|
||||||
} else if r.tasks.contains_key(&self.id) {
|
} else if r.tasks.contains_key(&self.id) {
|
||||||
println!("POLL: REPLACED WAKER FOR TASK: {}", self.id);
|
|
||||||
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
println!("POLL: REGISTERED TASK: {}, WAKER: {:?}", self.id, cx.waker());
|
|
||||||
r.register(self.data, cx.waker().clone(), self.id);
|
r.register(self.data, cx.waker().clone(), self.id);
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================== REACTOR ===================================
|
// =============================== REACTOR ===================================
|
||||||
enum TaskState {
|
enum TaskState {
|
||||||
Ready,
|
Ready,
|
||||||
@@ -3163,13 +3232,12 @@ impl Reactor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn wake(&mut self, id: usize) {
|
fn wake(&mut self, id: usize) {
|
||||||
self.tasks.get_mut(&id).map(|state| {
|
let state = self.tasks.get_mut(&id).unwrap();
|
||||||
match mem::replace(state, TaskState::Ready) {
|
match mem::replace(state, TaskState::Ready) {
|
||||||
TaskState::NotReady(waker) => waker.wake(),
|
TaskState::NotReady(waker) => waker.wake(),
|
||||||
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
||||||
@@ -3209,15 +3277,6 @@ I'll try my best to respond to each one of them.</p>
|
|||||||
<p>So our implementation has taken some obvious shortcuts and could use some improvement.
|
<p>So our implementation has taken some obvious shortcuts and could use some improvement.
|
||||||
Actually digging into the code and try things yourself is a good way to learn. Here are
|
Actually digging into the code and try things yourself is a good way to learn. Here are
|
||||||
some good exercises if you want to explore more:</p>
|
some good exercises if you want to explore more:</p>
|
||||||
<h3><a class="header" href="#avoid-threadpark" id="avoid-threadpark">Avoid <code>thread::park</code></a></h3>
|
|
||||||
<p>The big problem using <code>Thread::park</code> and <code>Thread::unpark</code> is that the user can access these
|
|
||||||
same methods from their own code. Try to use another method to suspend our thread and wake
|
|
||||||
it up again on our command. Some hints:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Check out <code>CondVars</code>, here are two sources <a href="https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables">Wikipedia</a> and the
|
|
||||||
docs for <a href="https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html"><code>CondVar</code></a></li>
|
|
||||||
<li>Take a look at crates that help you with this exact problem like <a href="https://github.com/crossbeam-rs/crossbeam">Crossbeam </a>(specifically the <a href="https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html"><code>Parker</code></a>)</li>
|
|
||||||
</ul>
|
|
||||||
<h3><a class="header" href="#avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around" id="avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around">Avoid wrapping the whole <code>Reactor</code> in a mutex and pass it around</a></h3>
|
<h3><a class="header" href="#avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around" id="avoid-wrapping-the-whole-reactor-in-a-mutex-and-pass-it-around">Avoid wrapping the whole <code>Reactor</code> in a mutex and pass it around</a></h3>
|
||||||
<p>First of all, protecting the whole <code>Reactor</code> and passing it around is overkill. We're only
|
<p>First of all, protecting the whole <code>Reactor</code> and passing it around is overkill. We're only
|
||||||
interested in synchronizing some parts of the information it contains. Try to refactor that
|
interested in synchronizing some parts of the information it contains. Try to refactor that
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -44,6 +44,8 @@ The first thing an `executor` does when it gets a `Future` is polling it.
|
|||||||
Rust provides a way for the Reactor and Executor to communicate through the `Waker`. The reactor stores this `Waker` and calls `Waker::wake()` on it once
|
Rust provides a way for the Reactor and Executor to communicate through the `Waker`. The reactor stores this `Waker` and calls `Waker::wake()` on it once
|
||||||
a `Future` has resolved and should be polled again.
|
a `Future` has resolved and should be polled again.
|
||||||
|
|
||||||
|
> Notice that this chapter has a bonus section called [A Proper Way to Park our Thread](./6_future_example.md#bonus-section---a-proper-way-to-park-our-thread) which shows how to avoid `thread::park`.
|
||||||
|
|
||||||
**Our Executor will look like this:**
|
**Our Executor will look like this:**
|
||||||
|
|
||||||
```rust, noplaypen, ignore
|
```rust, noplaypen, ignore
|
||||||
@@ -87,6 +89,14 @@ In all the examples you'll see in this chapter I've chosen to comment the code
|
|||||||
extensively. I find it easier to follow along that way so I'll not repeat myself
|
extensively. I find it easier to follow along that way so I'll not repeat myself
|
||||||
here and focus only on some important aspects that might need further explanation.
|
here and focus only on some important aspects that might need further explanation.
|
||||||
|
|
||||||
|
It's worth noting that simply calling `thread::sleep` as we do here can lead to
|
||||||
|
both deadlocks and errors. We'll explain a bit more later and fix this if you
|
||||||
|
read all the way to the [Bonus Section](./6_future_example.md##bonus-section---a-proper-way-to-park-our-thread) at
|
||||||
|
the end of this chapter.
|
||||||
|
|
||||||
|
For now, we keep it as simple and easy to understand as we can by just going
|
||||||
|
to sleep.
|
||||||
|
|
||||||
Now that you've read so much about `Generator`s and `Pin` already this should
|
Now that you've read so much about `Generator`s and `Pin` already this should
|
||||||
be rather easy to understand. `Future` is a state machine, every `await` point
|
be rather easy to understand. `Future` is a state machine, every `await` point
|
||||||
is a `yield` point. We could borrow data across `await` points and we meet the
|
is a `yield` point. We could borrow data across `await` points and we meet the
|
||||||
@@ -254,26 +264,9 @@ without passing around a reference.
|
|||||||
> ### Why using thread park/unpark is a bad idea for a library
|
> ### Why using thread park/unpark is a bad idea for a library
|
||||||
>
|
>
|
||||||
> It could deadlock easily since anyone could get a handle to the `executor thread`
|
> It could deadlock easily since anyone could get a handle to the `executor thread`
|
||||||
> and call park/unpark on it.
|
> and call park/unpark on our thread or we could have a race condition where the
|
||||||
>
|
> future resolves and calls `wake` before we have time to go to sleep in our
|
||||||
> 1. A future could call `unpark` on the executor thread from a different thread
|
> executor. We'll se how we can fix this at the end of this chapter.
|
||||||
> 2. Our `executor` thinks that data is ready and wakes up and polls the future
|
|
||||||
> 3. The future is not ready yet when polled, but at that exact same time the
|
|
||||||
> `Reactor` gets an event and calls `wake()` which also unparks our thread.
|
|
||||||
> 4. This could happen before we go to sleep again since these processes
|
|
||||||
> run in parallel.
|
|
||||||
> 5. Our reactor has called `wake` but our thread is still sleeping since it was
|
|
||||||
> awake already at that point.
|
|
||||||
> 6. We're deadlocked and our program stops working
|
|
||||||
|
|
||||||
> There is also the case that our thread could have what's called a
|
|
||||||
> `spurious wakeup` ([which can happen unexpectedly][spurious_wakeup]), which
|
|
||||||
> could cause the same deadlock if we're unlucky.
|
|
||||||
|
|
||||||
There are several better solutions, here are some:
|
|
||||||
|
|
||||||
- [std::sync::CondVar][condvar]
|
|
||||||
- [crossbeam::sync::Parker][crossbeam_parker]
|
|
||||||
|
|
||||||
## The Reactor
|
## The Reactor
|
||||||
|
|
||||||
@@ -481,14 +474,12 @@ fn main() {
|
|||||||
// our code into a state machine, `yielding` at every `await` point.
|
// our code into a state machine, `yielding` at every `await` point.
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = future1.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = future2.await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Our executor can only run one and one future, this is pretty normal
|
// Our executor can only run one and one future, this is pretty normal
|
||||||
@@ -741,6 +732,98 @@ do really hope that you do continue to explore further.
|
|||||||
|
|
||||||
Don't forget the exercises in the last chapter 😊.
|
Don't forget the exercises in the last chapter 😊.
|
||||||
|
|
||||||
|
## Bonus Section - a Proper Way to Park our Thread
|
||||||
|
|
||||||
|
As we explained earlier in our chapter, simply calling `thread::sleep` is not really
|
||||||
|
sufficient to implement a proper reactor. You can also reach a tool like the `Parker`
|
||||||
|
in crossbeam: [crossbeam::sync::Parker][crossbeam_parker]
|
||||||
|
|
||||||
|
Since it doesn't require many lines of code to create a working solution ourselves we'll show how
|
||||||
|
we can solve that by using a `Condvar` and a `Mutex` instead.
|
||||||
|
|
||||||
|
Start by implementing our own `Parker` like this:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
|
|
||||||
|
impl Parker {
|
||||||
|
fn park(&self) {
|
||||||
|
|
||||||
|
// We aquire a lock to the Mutex which protects our flag indicating if we
|
||||||
|
// should resume execution or not.
|
||||||
|
let mut resumable = self.0.lock().unwrap();
|
||||||
|
|
||||||
|
// We put this in a loop since there is a chance we'll get woken, but
|
||||||
|
// our flag hasn't changed. If that happens, we simply go back to sleep.
|
||||||
|
while !*resumable {
|
||||||
|
|
||||||
|
// We sleep until someone notifies us
|
||||||
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We immidiately set the condition to false, so that next time we call `park` we'll
|
||||||
|
// go right to sleep.
|
||||||
|
*resumable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
// We simply acquire a lock to our flag and sets the condition to `runnable` when we
|
||||||
|
// get it.
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
|
||||||
|
// We notify our `Condvar` so it wakes up and resumes.
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Condvar` in Rust is designed to work together with a Mutex. Usually, you'd think that we don't
|
||||||
|
release the mutex-lock we acquire in `self.0.lock().unwrap();` before we go to sleep. Which means
|
||||||
|
that our `unpark` function never will acquire a lock to our flag and we deadlock.
|
||||||
|
|
||||||
|
Using `Condvar` we avoid this since the `Condvar` will consume our lock so it's released at the
|
||||||
|
moment we go to sleep.
|
||||||
|
|
||||||
|
When we resume again, our `Condvar` returns our lock so we can continue to operate on it.
|
||||||
|
|
||||||
|
This means we need to make some very slight changes to our executor like this:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default()); // <--- NB!
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() }); <--- NB!
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(), // <--- NB!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And we need to change our `Waker` like this:
|
||||||
|
|
||||||
|
```rust, ignore
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyWaker {
|
||||||
|
parker: Arc<Parker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
|
waker_arc.parker.unpark();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's really all there is to it. The next chapter shows our finished code with this
|
||||||
|
improvement which you can explore further if you wish.
|
||||||
|
|
||||||
[mio]: https://github.com/tokio-rs/mio
|
[mio]: https://github.com/tokio-rs/mio
|
||||||
[arc_wake]: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.13/futures/task/trait.ArcWake.html
|
[arc_wake]: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.13/futures/task/trait.ArcWake.html
|
||||||
[example_repo]: https://github.com/cfsamson/examples-futures
|
[example_repo]: https://github.com/cfsamson/examples-futures
|
||||||
|
|||||||
@@ -8,19 +8,15 @@ run it yourself. Have fun!
|
|||||||
fn main() {
|
fn main() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let reactor = Reactor::new();
|
let reactor = Reactor::new();
|
||||||
let future1 = Task::new(reactor.clone(), 1, 1);
|
|
||||||
let future2 = Task::new(reactor.clone(), 2, 2);
|
|
||||||
|
|
||||||
let fut1 = async {
|
let fut1 = async {
|
||||||
let val = future1.await;
|
let val = Task::new(reactor.clone(), 1, 1).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let fut2 = async {
|
let fut2 = async {
|
||||||
let val = future2.await;
|
let val = Task::new(reactor.clone(), 2, 2).await;
|
||||||
let dur = (Instant::now() - start).as_secs_f32();
|
println!("Got {} at time: {:.2}.", val, start.elapsed().as_secs_f32());
|
||||||
println!("Future got {} at time: {:.2}.", val, dur);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mainfut = async {
|
let mainfut = async {
|
||||||
@@ -31,35 +27,50 @@ fn main() {
|
|||||||
block_on(mainfut);
|
block_on(mainfut);
|
||||||
reactor.lock().map(|mut r| r.close()).unwrap();
|
reactor.lock().map(|mut r| r.close()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
|
future::Future, sync::{ mpsc::{channel, Sender}, Arc, Mutex, Condvar},
|
||||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
|
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem, pin::Pin,
|
||||||
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================= EXECUTOR ====================================
|
// ============================= EXECUTOR ====================================
|
||||||
fn block_on<F: Future>(mut future: F) -> F::Output {
|
#[derive(Default)]
|
||||||
let mywaker = Arc::new(MyWaker {
|
struct Parker(Mutex<bool>, Condvar);
|
||||||
thread: thread::current(),
|
|
||||||
});
|
|
||||||
let waker = waker_into_waker(Arc::into_raw(mywaker));
|
|
||||||
let mut cx = Context::from_waker(&waker);
|
|
||||||
|
|
||||||
// SAFETY: we shadow `future` so it can't be accessed again.
|
impl Parker {
|
||||||
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
fn park(&self) {
|
||||||
let val = loop {
|
let mut resumable = self.0.lock().unwrap();
|
||||||
match Future::poll(future.as_mut(), &mut cx) {
|
while !*resumable {
|
||||||
Poll::Ready(val) => break val,
|
resumable = self.1.wait(resumable).unwrap();
|
||||||
Poll::Pending => thread::park(),
|
}
|
||||||
};
|
*resumable = false;
|
||||||
};
|
}
|
||||||
val
|
|
||||||
|
fn unpark(&self) {
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
self.1.notify_one();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||||
|
let parker = Arc::new(Parker::default());
|
||||||
|
let mywaker = Arc::new(MyWaker { parker: parker.clone() });
|
||||||
|
let waker = mywaker_into_waker(Arc::into_raw(mywaker));
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
|
||||||
|
// SAFETY: we shadow `future` so it can't be accessed again.
|
||||||
|
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||||
|
loop {
|
||||||
|
match Future::poll(future.as_mut(), &mut cx) {
|
||||||
|
Poll::Ready(val) => break val,
|
||||||
|
Poll::Pending => parker.park(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
// ====================== FUTURE IMPLEMENTATION ==============================
|
// ====================== FUTURE IMPLEMENTATION ==============================
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MyWaker {
|
struct MyWaker {
|
||||||
thread: thread::Thread,
|
parker: Arc<Parker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -70,9 +81,8 @@ pub struct Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_wake(s: &MyWaker) {
|
fn mywaker_wake(s: &MyWaker) {
|
||||||
let waker_ptr: *const MyWaker = s;
|
let waker_arc = unsafe { Arc::from_raw(s) };
|
||||||
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
|
waker_arc.parker.unpark();
|
||||||
waker_arc.thread.unpark();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||||
@@ -90,7 +100,7 @@ const VTABLE: RawWakerVTable = unsafe {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
fn waker_into_waker(s: *const MyWaker) -> Waker {
|
fn mywaker_into_waker(s: *const MyWaker) -> Waker {
|
||||||
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||||
unsafe { Waker::from_raw(raw_waker) }
|
unsafe { Waker::from_raw(raw_waker) }
|
||||||
}
|
}
|
||||||
@@ -106,21 +116,17 @@ impl Future for Task {
|
|||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
let mut r = self.reactor.lock().unwrap();
|
let mut r = self.reactor.lock().unwrap();
|
||||||
if r.is_ready(self.id) {
|
if r.is_ready(self.id) {
|
||||||
println!("POLL: TASK {} IS READY", self.id);
|
|
||||||
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
||||||
Poll::Ready(self.id)
|
Poll::Ready(self.id)
|
||||||
} else if r.tasks.contains_key(&self.id) {
|
} else if r.tasks.contains_key(&self.id) {
|
||||||
println!("POLL: REPLACED WAKER FOR TASK: {}", self.id);
|
|
||||||
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
println!("POLL: REGISTERED TASK: {}, WAKER: {:?}", self.id, cx.waker());
|
|
||||||
r.register(self.data, cx.waker().clone(), self.id);
|
r.register(self.data, cx.waker().clone(), self.id);
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================== REACTOR ===================================
|
// =============================== REACTOR ===================================
|
||||||
enum TaskState {
|
enum TaskState {
|
||||||
Ready,
|
Ready,
|
||||||
@@ -172,13 +178,12 @@ impl Reactor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn wake(&mut self, id: usize) {
|
fn wake(&mut self, id: usize) {
|
||||||
self.tasks.get_mut(&id).map(|state| {
|
let state = self.tasks.get_mut(&id).unwrap();
|
||||||
match mem::replace(state, TaskState::Ready) {
|
match mem::replace(state, TaskState::Ready) {
|
||||||
TaskState::NotReady(waker) => waker.wake(),
|
TaskState::NotReady(waker) => waker.wake(),
|
||||||
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
|
||||||
|
|||||||
@@ -17,16 +17,6 @@ So our implementation has taken some obvious shortcuts and could use some improv
|
|||||||
Actually digging into the code and try things yourself is a good way to learn. Here are
|
Actually digging into the code and try things yourself is a good way to learn. Here are
|
||||||
some good exercises if you want to explore more:
|
some good exercises if you want to explore more:
|
||||||
|
|
||||||
### Avoid `thread::park`
|
|
||||||
|
|
||||||
The big problem using `Thread::park` and `Thread::unpark` is that the user can access these
|
|
||||||
same methods from their own code. Try to use another method to suspend our thread and wake
|
|
||||||
it up again on our command. Some hints:
|
|
||||||
|
|
||||||
* Check out `CondVars`, here are two sources [Wikipedia][condvar_wiki] and the
|
|
||||||
docs for [`CondVar`][condvar_std]
|
|
||||||
* Take a look at crates that help you with this exact problem like [Crossbeam ](https://github.com/crossbeam-rs/crossbeam)\(specifically the [`Parker`](https://docs.rs/crossbeam/0.7.3/crossbeam/sync/struct.Parker.html)\)
|
|
||||||
|
|
||||||
### Avoid wrapping the whole `Reactor` in a mutex and pass it around
|
### Avoid wrapping the whole `Reactor` in a mutex and pass it around
|
||||||
|
|
||||||
First of all, protecting the whole `Reactor` and passing it around is overkill. We're only
|
First of all, protecting the whole `Reactor` and passing it around is overkill. We're only
|
||||||
|
|||||||
Reference in New Issue
Block a user