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:
Carl Fredrik Samson
2020-04-13 14:16:32 +02:00
parent f4b2029788
commit d9eb756ef7
10 changed files with 441 additions and 240 deletions

View File

@@ -158,19 +158,15 @@ run it yourself. Have fun!</p>
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
let start = Instant::now();
let reactor = Reactor::new();
let future1 = Task::new(reactor.clone(), 1, 1);
let future2 = Task::new(reactor.clone(), 2, 2);
let fut1 = async {
let val = future1.await;
let dur = (Instant::now() - start).as_secs_f32();
println!(&quot;Future got {} at time: {:.2}.&quot;, val, dur);
let val = Task::new(reactor.clone(), 1, 1).await;
println!(&quot;Got {} at time: {:.2}.&quot;, val, start.elapsed().as_secs_f32());
};
let fut2 = async {
let val = future2.await;
let dur = (Instant::now() - start).as_secs_f32();
println!(&quot;Future got {} at time: {:.2}.&quot;, val, dur);
let val = Task::new(reactor.clone(), 2, 2).await;
println!(&quot;Got {} at time: {:.2}.&quot;, val, start.elapsed().as_secs_f32());
};
let mainfut = async {
@@ -181,35 +177,50 @@ run it yourself. Have fun!</p>
block_on(mainfut);
reactor.lock().map(|mut r| r.close()).unwrap();
}
use std::{
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
future::Future, sync::{ mpsc::{channel, Sender}, Arc, Mutex, Condvar},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem, pin::Pin,
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
};
// ============================= EXECUTOR ====================================
fn block_on&lt;F: Future&gt;(mut future: F) -&gt; F::Output {
let mywaker = Arc::new(MyWaker {
thread: thread::current(),
});
let waker = waker_into_waker(Arc::into_raw(mywaker));
let mut cx = Context::from_waker(&amp;waker);
#[derive(Default)]
struct Parker(Mutex&lt;bool&gt;, Condvar);
// SAFETY: we shadow `future` so it can't be accessed again.
let mut future = unsafe { Pin::new_unchecked(&amp;mut future) };
let val = loop {
match Future::poll(future.as_mut(), &amp;mut cx) {
Poll::Ready(val) =&gt; break val,
Poll::Pending =&gt; thread::park(),
};
};
val
impl Parker {
fn park(&amp;self) {
let mut resumable = self.0.lock().unwrap();
while !*resumable {
resumable = self.1.wait(resumable).unwrap();
}
*resumable = false;
}
fn unpark(&amp;self) {
*self.0.lock().unwrap() = true;
self.1.notify_one();
}
}
fn block_on&lt;F: Future&gt;(mut future: F) -&gt; 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(&amp;waker);
// SAFETY: we shadow `future` so it can't be accessed again.
let mut future = unsafe { Pin::new_unchecked(&amp;mut future) };
loop {
match Future::poll(future.as_mut(), &amp;mut cx) {
Poll::Ready(val) =&gt; break val,
Poll::Pending =&gt; parker.park(),
};
}
}
// ====================== FUTURE IMPLEMENTATION ==============================
#[derive(Clone)]
struct MyWaker {
thread: thread::Thread,
parker: Arc&lt;Parker&gt;,
}
#[derive(Clone)]
@@ -220,9 +231,8 @@ pub struct Task {
}
fn mywaker_wake(s: &amp;MyWaker) {
let waker_ptr: *const MyWaker = s;
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
waker_arc.thread.unpark();
let waker_arc = unsafe { Arc::from_raw(s) };
waker_arc.parker.unpark();
}
fn mywaker_clone(s: &amp;MyWaker) -&gt; RawWaker {
@@ -240,7 +250,7 @@ const VTABLE: RawWakerVTable = unsafe {
)
};
fn waker_into_waker(s: *const MyWaker) -&gt; Waker {
fn mywaker_into_waker(s: *const MyWaker) -&gt; Waker {
let raw_waker = RawWaker::new(s as *const (), &amp;VTABLE);
unsafe { Waker::from_raw(raw_waker) }
}
@@ -256,21 +266,17 @@ impl Future for Task {
fn poll(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
let mut r = self.reactor.lock().unwrap();
if r.is_ready(self.id) {
println!(&quot;POLL: TASK {} IS READY&quot;, self.id);
*r.tasks.get_mut(&amp;self.id).unwrap() = TaskState::Finished;
Poll::Ready(self.id)
} else if r.tasks.contains_key(&amp;self.id) {
println!(&quot;POLL: REPLACED WAKER FOR TASK: {}&quot;, self.id);
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
Poll::Pending
} else {
println!(&quot;POLL: REGISTERED TASK: {}, WAKER: {:?}&quot;, self.id, cx.waker());
r.register(self.data, cx.waker().clone(), self.id);
Poll::Pending
}
}
}
// =============================== REACTOR ===================================
enum TaskState {
Ready,
@@ -322,13 +328,12 @@ impl Reactor {
}
fn wake(&amp;mut self, id: usize) {
self.tasks.get_mut(&amp;id).map(|state| {
match mem::replace(state, TaskState::Ready) {
TaskState::NotReady(waker) =&gt; waker.wake(),
TaskState::Finished =&gt; panic!(&quot;Called 'wake' twice on task: {}&quot;, id),
_ =&gt; unreachable!()
}
}).unwrap();
let state = self.tasks.get_mut(&amp;id).unwrap();
match mem::replace(state, TaskState::Ready) {
TaskState::NotReady(waker) =&gt; waker.wake(),
TaskState::Finished =&gt; panic!(&quot;Called 'wake' twice on task: {}&quot;, id),
_ =&gt; unreachable!()
}
}
fn register(&amp;mut self, duration: u64, waker: Waker, id: usize) {