Merge branch 'master' into typo-future

This commit is contained in:
Carl Fredrik Samson
2020-04-11 00:27:21 +02:00
committed by GitHub
23 changed files with 1163 additions and 1018 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
./book
book/
./.vscode
+4
View File
@@ -43,6 +43,10 @@ Feedback, questions or discussion is welcome in the issue tracker.
**2020-04-06:** Final draft finished
**2020-04-10:** Rather substantial rewrite of the `Reactor` to better the
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.
## License
This book is MIT licensed.
+13 -12
View File
@@ -216,11 +216,11 @@ fn main() {
<p>First, for computers to be <a href="https://en.wikipedia.org/wiki/Efficiency"><em>efficient</em></a> they need to multitask. Once you
start to look under the covers (like <a href="https://os.phil-opp.com/async-await/">how an operating system works</a>)
you'll see concurrency everywhere. It's very fundamental in everything we do.</p>
<p>Second, we have the web. </p>
<p>Secondly, we have the web.</p>
<p>Web servers are all about I/O and handling small tasks
(requests). When the number of small tasks is large it's not a good fit for OS
threads as of today because of the memory they require and the overhead involved
when creating new threads. </p>
when creating new threads.</p>
<p>This gets even more problematic when the load is variable which means the current number of tasks a
program has at any point in time is unpredictable. That's why you'll see so many async web
frameworks and database drivers today.</p>
@@ -238,7 +238,7 @@ task(thread) to another by doing a &quot;context switch&quot;.</p>
such a system) which then continues running a different task.</p>
<p>Rust had green threads once, but they were removed before it hit 1.0. The state
of execution is stored in each stack so in such a solution there would be no
need for <code>async</code>, <code>await</code>, <code>Futures</code> or <code>Pin</code>. </p>
need for <code>async</code>, <code>await</code>, <code>Future</code> or <code>Pin</code>.</p>
<p><strong>The typical flow looks like this:</strong></p>
<ol>
<li>Run some non-blocking code.</li>
@@ -279,7 +279,7 @@ in that book. The code below is wildly unsafe and it's just to show a real examp
It's not in any way meant to showcase &quot;best practice&quot;. Just so we're on
the same page.</p>
</blockquote>
<p><em><strong>Press the expand icon in the top right corner to show the example code.</strong></em> </p>
<p><em><strong>Press the expand icon in the top right corner to show the example code.</strong></em></p>
<pre><pre class="playpen"><code class="language-rust edition2018"><span class="boring">#![feature(asm, naked_functions)]
</span><span class="boring">use std::ptr;
</span><span class="boring">
@@ -383,7 +383,7 @@ the same page.</p>
</span><span class="boring"> return false;
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> if self.threads[self.current].state != State::Available {
</span><span class="boring"> self.threads[self.current].state = State::Ready;
</span><span class="boring"> }
@@ -406,7 +406,7 @@ the same page.</p>
</span><span class="boring"> .iter_mut()
</span><span class="boring"> .find(|t| t.state == State::Available)
</span><span class="boring"> .expect(&quot;no available thread.&quot;);
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> let size = available.stack.len();
</span><span class="boring"> let s_ptr = available.stack.as_mut_ptr();
</span><span class="boring"> available.task = Some(Box::new(f));
@@ -496,7 +496,7 @@ difficult to understand. If I hadn't written it myself I would probably feel
the same. You can always go back and read the book which explains it later.</p>
<h2><a class="header" href="#callback-based-approaches" id="callback-based-approaches">Callback based approaches</a></h2>
<p>You probably already know what we're going to talk about in the next paragraphs
from JavaScript which I assume most know. </p>
from JavaScript which I assume most know.</p>
<blockquote>
<p>If your exposure to JavaScript callbacks has given you any sorts of PTSD earlier
in life, close your eyes now and scroll down for 2-3 seconds. You'll find a link
@@ -603,8 +603,8 @@ same thread using this example. The OS threads we create are basically just used
as timers but could represent any kind of resource that we'll have to wait for.</p>
<h2><a class="header" href="#from-callbacks-to-promises" id="from-callbacks-to-promises">From callbacks to promises</a></h2>
<p>You might start to wonder by now, when are we going to talk about Futures?</p>
<p>Well, we're getting there. You see <code>promises</code>, <code>futures</code> and other names for
deferred computations are often used interchangeably. </p>
<p>Well, we're getting there. You see Promises, Futures and other names for
deferred computations are often used interchangeably.</p>
<p>There are formal differences between them, but we won't cover those
here. It's worth explaining <code>promises</code> a bit since they're widely known due to
their use in JavaScript. Promises also have a lot in common with Rust's Futures.</p>
@@ -632,8 +632,8 @@ timer(200)
.then(() =&gt; console.log(&quot;I'm the last one&quot;));
</code></pre>
<p>The change is even more substantial under the hood. You see, promises return
a state machine which can be in one of three states: <code>pending</code>, <code>fulfilled</code> or
<code>rejected</code>. </p>
a state machine which can be in one of three states: <code>pending</code>, <code>fulfilled</code> or
<code>rejected</code>.</p>
<p>When we call <code>timer(200)</code> in the sample above, we get back a promise in the state <code>pending</code>.</p>
<p>Since promises are re-written as state machines, they also enable an even better
syntax which allows us to write our last example like this:</p>
@@ -661,9 +661,10 @@ running a task. Rust's Futures on the other hand are <em>lazily</em> evaluated.
need to be polled once before they do any work.</p>
</blockquote>
<br />
<div style="text-align: center; padding-top: 2em;">
<div style="text-align: center; padding-top: 2em;">
<a href="/books-futures-explained/1_futures_in_rust.html" style="background: red; color: white; padding:2em 2em 2em 2em; font-size: 1.2em;"><strong>PANIC BUTTON (next chapter)</strong></a>
</div>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
+12 -12
View File
@@ -197,7 +197,7 @@ a runtime, but we'll go through how they're constructed in this book as well.</p
<p>It's also unlikely that you'll pass a leaf-future to a runtime and run it to
completion alone as you'll understand by reading the next paragraph.</p>
<h3><a class="header" href="#non-leaf-futures" id="non-leaf-futures">Non-leaf-futures</a></h3>
<p>Non-leaf-futures is the kind of futures we as <em>users</em> of a runtime writes
<p>Non-leaf-futures is the kind of futures we as <em>users</em> of a runtime write
ourselves using the <code>async</code> keyword to create a <strong>task</strong> which can be run on the
executor.</p>
<p>The bulk of an async program will consist of non-leaf-futures, which are a kind
@@ -225,7 +225,7 @@ seem a bit strange to you.</p>
<p>Rust is different from these languages in the sense that Rust doesn't come with
a runtime for handling concurrency, so you need to use a library which provide
this for you.</p>
<p>Quite a bit of complexity attributed to <code>Futures</code> is actually complexity rooted
<p>Quite a bit of complexity attributed to Futures is actually complexity rooted
in runtimes. Creating an efficient runtime is hard.</p>
<p>Learning how to use one correctly requires quite a bit of effort as well, but
you'll see that there are several similarities between these kind of runtimes so
@@ -242,8 +242,8 @@ you'll just use the one provided for you.</p>
notifying a <code>Future</code> that it can do more work, and actually doing the work
on the <code>Future</code>.</p>
<p>You can think of the former as the reactor's job, and the latter as the
executors job. These two parts of a runtime interacts using the <code>Waker</code> type.</p>
<p>The two most popular runtimes for <code>Futures</code> as of writing this is:</p>
executors job. These two parts of a runtime interact with each other using the <code>Waker</code> type.</p>
<p>The two most popular runtimes for Futures as of writing this is:</p>
<ul>
<li><a href="https://github.com/async-rs/async-std">async-std</a></li>
<li><a href="https://github.com/tokio-rs/tokio">Tokio</a></li>
@@ -263,17 +263,17 @@ of non-blocking I/O, how these tasks are created or how they're run.</p>
take a look at this async block using pseudo-rust as example:</p>
<pre><code class="language-rust ignore">let non_leaf = async {
let mut stream = TcpStream::connect(&quot;127.0.0.1:3000&quot;).await.unwrap(); // &lt;-- yield
// request a large dataset
let result = stream.write(get_dataset_request).await.unwrap(); // &lt;-- yield
// wait for the dataset
let mut response = vec![];
stream.read(&amp;mut response).await.unwrap(); // &lt;-- yield
// do some CPU-intensive analysis on the dataset
let report = analyzer::analyze_data(response).unwrap();
// send the results back
stream.write(report).await.unwrap(); // &lt;-- yield
};
@@ -311,13 +311,13 @@ to the thread-pool most runtimes provide.</p>
can either perform CPU-intensive tasks or &quot;blocking&quot; tasks which is not supported
by the runtime.</p>
<p>Now, armed with this knowledge you are already on a good way for understanding
Futures, but we're not gonna stop yet, there is lots of details to cover. </p>
Futures, but we're not gonna stop yet, there is lots of details to cover.</p>
<p>Take a break or a cup of coffe and get ready as we go for a deep dive in the next chapters.</p>
<h2><a class="header" href="#bonus-section" id="bonus-section">Bonus section</a></h2>
<p>If you find the concepts of concurrency and async programming confusing in
general, I know where you're coming from and I have written some resources to
try to give a high level overview that will make it easier to learn Rusts
<code>Futures</code> afterwards:</p>
general, I know where you're coming from and I have written some resources to
try to give a high level overview that will make it easier to learn Rusts
Futures afterwards:</p>
<ul>
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html">Async Basics - The difference between concurrency and parallelism</a></li>
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html">Async Basics - Async history</a></li>
@@ -325,7 +325,7 @@ try to give a high level overview that will make it easier to learn Rusts
<li><a href="https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html">Async Basics - Epoll, Kqueue and IOCP</a></li>
</ul>
<p>Learning these concepts by studying futures is making it much harder than
it needs to be, so go on and read these chapters if you feel a bit unsure. </p>
it needs to be, so go on and read these chapters if you feel a bit unsure.</p>
<p>I'll be right here when you're back.</p>
<p>However, if you feel that you have the basics covered, then let's get moving!</p>
+3 -3
View File
@@ -180,19 +180,19 @@ recommend <a href="https://boats.gitlab.io/blog/post/wakers-i/">Withoutboats art
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.</p>
<h2><a class="header" href="#understanding-the-waker" id="understanding-the-waker">Understanding the <code>Waker</code></a></h2>
<p>One of the most confusing things we encounter when implementing our own <code>Futures</code>
<p>One of the most confusing things we encounter when implementing our own <code>Future</code>s
is how we implement a <code>Waker</code> . Creating a <code>Waker</code> involves creating a <code>vtable</code>
which allows us to use dynamic dispatch to call methods on a <em>type erased</em> trait
object we construct our selves.</p>
<blockquote>
<p>If you want to know more about dynamic dispatch in Rust I can recommend an
<p>If you want to know more about dynamic dispatch in Rust I can recommend an
article written by Adam Schwalm called <a href="https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/">Exploring Dynamic Dispatch in Rust</a>.</p>
</blockquote>
<p>Let's explain this a bit more in detail.</p>
<h2><a class="header" href="#fat-pointers-in-rust" id="fat-pointers-in-rust">Fat pointers in Rust</a></h2>
<p>To get a better understanding of how we implement the <code>Waker</code> in Rust, we need
to take a step back and talk about some fundamentals. Let's start by taking a
look at the size of some different pointer types in Rust. </p>
look at the size of some different pointer types in Rust.</p>
<p>Run the following code <em>(You'll have to press &quot;play&quot; to see the output)</em>:</p>
<pre><pre class="playpen"><code class="language-rust"><span class="boring">use std::mem::size_of;
</span>trait SomeTrait { }
+23 -23
View File
@@ -160,7 +160,7 @@
<li>See first hand why we need <code>Pin</code></li>
<li>Understand what makes Rusts async model very memory efficient</li>
</ul>
<p>The motivation for <code>Generators</code> can be found in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md">RFC#2033</a>. It's very
<p>The motivation for <code>Generator</code>s can be found in <a href="https://github.com/rust-lang/rfcs/blob/master/text/2033-experimental-coroutines.md">RFC#2033</a>. It's very
well written and I can recommend reading through it (it talks as much about
async/await as it does about generators).</p>
</blockquote>
@@ -185,7 +185,7 @@ handle concurrency:</p>
so we won't repeat that here. We'll concentrate on the variants of stackless
coroutines which Rust uses today.</p>
<h3><a class="header" href="#combinators" id="combinators">Combinators</a></h3>
<p><code>Futures 0.1</code> used combinators. If you've worked with <code>Promises</code> in JavaScript,
<p><code>Futures 0.1</code> used combinators. If you've worked with Promises in JavaScript,
you already know combinators. In Rust they look like this:</p>
<pre><code class="language-rust noplaypen ignore">let future = Connection::connect(conn_str).and_then(|conn| {
conn.query(&quot;somerequest&quot;).map(|row|{
@@ -230,7 +230,7 @@ async/await as keywords (it can even be done using a macro).</li>
</code></pre>
<p>Async in Rust is implemented using Generators. So to understand how async really
works we need to understand generators first. Generators in Rust are implemented
as state machines. </p>
as state machines.</p>
<p>The memory footprint of a chain of computations is defined by <em>the largest footprint
that a single step requires</em>.</p>
<p>That means that adding steps to a chain of computations might not require any
@@ -333,7 +333,7 @@ impl Generator for GeneratorA {
</blockquote>
<p>Now that you know that the <code>yield</code> keyword in reality rewrites your code to become a state machine,
you'll also know the basics of how <code>await</code> works. It's very similar.</p>
<p>Now, there are some limitations in our naive state machine above. What happens when you have a
<p>Now, there are some limitations in our naive state machine above. What happens when you have a
<code>borrow</code> across a <code>yield</code> point?</p>
<p>We could forbid that, but <strong>one of the major design goals for the async/await syntax has been
to allow this</strong>. These kinds of borrows were not possible using <code>Futures 0.1</code> so we can't let this
@@ -351,7 +351,7 @@ in depth explanation see <a href="https://tmandry.gitlab.io/blog/posts/optimizin
};
</code></pre>
<p>We'll be hand-coding some versions of a state-machines representing a state
machine for the generator defined aboce.</p>
machine for the generator defined above.</p>
<p>We step through each step &quot;manually&quot; in every example, so it looks pretty
unfamiliar. We could add some syntactic sugar like implementing the <code>Iterator</code>
trait for our generators which would let us do this:</p>
@@ -366,7 +366,7 @@ Just keep this in the back of your head as we move forward.</p>
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span><span class="boring">enum GeneratorState&lt;Y, R&gt; {
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Complete(R),
</span><span class="boring">}
</span><span class="boring">
@@ -430,8 +430,8 @@ into itself.</p>
<span class="boring">#![allow(unused_variables)]
</span><span class="boring">fn main() {
</span>enum GeneratorState&lt;Y, R&gt; {
Yielded(Y),
Complete(R),
Yielded(Y),
Complete(R),
}
trait Generator {
@@ -464,12 +464,12 @@ impl Generator for GeneratorA {
let borrowed = &amp;to_borrow;
let res = borrowed.len();
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
// NB! And we set the pointer to reference the to_borrow string here
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
*borrowed = to_borrow;
}
GeneratorState::Yielded(res)
}
@@ -512,8 +512,8 @@ does what we'd expect. But there is still one huge problem with this:</p>
};
}
<span class="boring">enum GeneratorState&lt;Y, R&gt; {
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Complete(R),
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Complete(R),
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">trait Generator {
@@ -546,12 +546,12 @@ does what we'd expect. But there is still one huge problem with this:</p>
</span><span class="boring"> let borrowed = &amp;to_borrow;
</span><span class="boring"> let res = borrowed.len();
</span><span class="boring"> *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> // We set the self-reference here
</span><span class="boring"> if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
</span><span class="boring"> *borrowed = to_borrow;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> GeneratorState::Yielded(res)
</span><span class="boring"> }
</span><span class="boring">
@@ -590,8 +590,8 @@ does what we'd expect. But there is still one huge problem with this:</p>
};
}
<span class="boring">enum GeneratorState&lt;Y, R&gt; {
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Complete(R),
</span><span class="boring"> Yielded(Y),
</span><span class="boring"> Complete(R),
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">trait Generator {
@@ -624,12 +624,12 @@ does what we'd expect. But there is still one huge problem with this:</p>
</span><span class="boring"> let borrowed = &amp;to_borrow;
</span><span class="boring"> let res = borrowed.len();
</span><span class="boring"> *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> // We set the self-reference here
</span><span class="boring"> if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
</span><span class="boring"> *borrowed = to_borrow;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> GeneratorState::Yielded(res)
</span><span class="boring"> }
</span><span class="boring">
@@ -651,7 +651,7 @@ while using just safe Rust. This is a big problem!</p>
<blockquote>
<p>I've actually forced the code above to use the nightly version of the compiler.
If you run <a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=5cbe9897c0e23a502afd2740c7e78b98">the example above on the playground</a>,
you'll see that it runs without panicing on the current stable (1.42.0) but
you'll see that it runs without panicking on the current stable (1.42.0) but
panics on the current nightly (1.44.0). Scary!</p>
</blockquote>
<p>We'll explain exactly what happened here using a slightly simpler example in the next
@@ -663,7 +663,7 @@ by looking at how generators and the async keyword is related.</p>
<h2><a class="header" href="#async-and-generators" id="async-and-generators">Async and generators</a></h2>
<p>Futures in Rust are implemented as state machines much the same way Generators
are state machines.</p>
<p>You might have noticed the similarites in the syntax used in async blocks and
<p>You might have noticed the similarities in the syntax used in async blocks and
the syntax used in generators:</p>
<pre><code class="language-rust ignore">let mut gen = move || {
let to_borrow = String::from(&quot;Hello&quot;);
@@ -688,7 +688,7 @@ a Future works and the way a Generator work internally is similar.</p>
returning <code>Yielded</code> or <code>Complete</code> it returns <code>Pending</code> or <code>Ready</code>. Each <code>await</code>
point in a future is like a <code>yield</code> point in a generator.</p>
<p>Do you see how they're connected now?</p>
<p>Thats why kowing how generators work and the challanges they pose also teaches
<p>Thats why knowing how generators work and the challenges they pose also teaches
you how futures work and the challenges we need to tackle when working with them.</p>
<p>The same goes for the challenges of borrowing across yield/await points.</p>
<h2><a class="header" href="#bonus-section---self-referential-generators-in-rust-today" id="bonus-section---self-referential-generators-in-rust-today">Bonus section - self referential generators in Rust today</a></h2>
@@ -712,7 +712,7 @@ pub fn main() {
yield borrowed.len();
println!(&quot;{} world!&quot;, borrowed);
};
let gen2 = static || {
let to_borrow = String::from(&quot;Hello&quot;);
let borrowed = &amp;to_borrow;
@@ -726,7 +726,7 @@ pub fn main() {
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume(()) {
println!(&quot;Gen1 got value {}&quot;, n);
}
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume(()) {
println!(&quot;Gen2 got value {}&quot;, n);
};
+27 -27
View File
@@ -180,7 +180,7 @@ that it's time to lay down the work and start over tomorrow with a fresh mind.</
</blockquote>
<p>On a more serious note, I feel obliged to mention that there are valid reasons
for the names that were chosen. Naming is not easy, and I considered renaming
<code>Unpin</code> and <code>!Unpin</code> in this book to make them easier to reason about. </p>
<code>Unpin</code> and <code>!Unpin</code> in this book to make them easier to reason about.</p>
<p>However, an experienced member of the Rust community convinced me that that there
is just too many nuances and edge-cases to consider which is easily overlooked when
naively giving these markers different names, and I'm convinced that we'll
@@ -214,11 +214,11 @@ impl Test {
let self_ref: *const String = &amp;self.a;
self.b = self_ref;
}
fn a(&amp;self) -&gt; &amp;str {
&amp;self.a
}
}
fn b(&amp;self) -&gt; &amp;String {
unsafe {&amp;*(self.b)}
}
@@ -264,11 +264,11 @@ you see, this works as expected:</p>
</span><span class="boring"> let self_ref: *const String = &amp;self.a;
</span><span class="boring"> self.b = self_ref;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> fn a(&amp;self) -&gt; &amp;str {
</span><span class="boring"> &amp;self.a
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn b(&amp;self) -&gt; &amp;String {
</span><span class="boring"> unsafe {&amp;*(self.b)}
</span><span class="boring"> }
@@ -313,11 +313,11 @@ which <code>test1</code> is pointing to with the data stored at the memory locat
</span><span class="boring"> let self_ref: *const String = &amp;self.a;
</span><span class="boring"> self.b = self_ref;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> fn a(&amp;self) -&gt; &amp;str {
</span><span class="boring"> &amp;self.a
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn b(&amp;self) -&gt; &amp;String {
</span><span class="boring"> unsafe {&amp;*(self.b)}
</span><span class="boring"> }
@@ -369,11 +369,11 @@ be tied to the lifetime of <code>test2</code> anymore.</p>
</span><span class="boring"> let self_ref: *const String = &amp;self.a;
</span><span class="boring"> self.b = self_ref;
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring">
</span><span class="boring"> fn a(&amp;self) -&gt; &amp;str {
</span><span class="boring"> &amp;self.a
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn b(&amp;self) -&gt; &amp;String {
</span><span class="boring"> unsafe {&amp;*(self.b)}
</span><span class="boring"> }
@@ -384,7 +384,7 @@ it's easy to create serious bugs using this code.</p>
<p>I created a diagram to help visualize what's going on:</p>
<p><strong>Fig 1: Before and after swap</strong>
<img src="./assets/swap_problem.jpg" alt="swap_problem" /></p>
<p>As you can see this results in unwanted behavior. It's easy to get this to
<p>As you can see this results in unwanted behavior. It's easy to get this to
segfault, show UB and fail in other spectacular ways as well.</p>
<h2><a class="header" href="#pinning-to-the-stack" id="pinning-to-the-stack">Pinning to the stack</a></h2>
<p>Now, we can solve this problem by using <code>Pin</code> instead. Let's take a look at what
@@ -437,7 +437,7 @@ we'll show in a second.</p>
// Notice how we shadow `test1` to prevent it from beeing accessed again
let mut test1 = unsafe { Pin::new_unchecked(&amp;mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new(&quot;test2&quot;);
let mut test2 = unsafe { Pin::new_unchecked(&amp;mut test2) };
Test::init(test2.as_mut());
@@ -487,13 +487,13 @@ you'll get a compilation error.</p>
let mut test1 = Test::new(&quot;test1&quot;);
let mut test1 = unsafe { Pin::new_unchecked(&amp;mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new(&quot;test2&quot;);
let mut test2 = unsafe { Pin::new_unchecked(&amp;mut test2) };
Test::init(test2.as_mut());
println!(&quot;a: {}, b: {}&quot;, Test::a(test1.as_ref()), Test::b(test1.as_ref()));
std::mem::swap(test1.as_mut(), test2.as_mut());
std::mem::swap(test1.get_mut(), test2.get_mut());
println!(&quot;a: {}, b: {}&quot;, Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}
<span class="boring">use std::pin::Pin;
@@ -539,7 +539,7 @@ us from swapping the pinned pointers.</p>
stack frame we're in, so we can't create a self referential object in one
stack frame and return it since any pointers we take to &quot;self&quot; is invalidated.</p>
<p>It also puts a lot of responsibility in your hands if you pin a value to the
stack. A mistake that is easy to make is, forgetting to shadow the original variable
stack. A mistake that is easy to make is, forgetting to shadow the original variable
since you could drop the pinned pointer and access the old value
after it's initialized like this:</p>
<pre><pre class="playpen"><code class="language-rust">fn main() {
@@ -547,7 +547,7 @@ after it's initialized like this:</p>
let mut test1_pin = unsafe { Pin::new_unchecked(&amp;mut test1) };
Test::init(test1_pin.as_mut());
drop(test1_pin);
let mut test2 = Test::new(&quot;test2&quot;);
mem::swap(&amp;mut test1, &amp;mut test2);
println!(&quot;Not self referential anymore: {:?}&quot;, test1.b);
@@ -662,7 +662,7 @@ certain operations on this value.</p>
</li>
<li>
<p>Most standard library types implement <code>Unpin</code>. The same goes for most
&quot;normal&quot; types you encounter in Rust. <code>Futures</code> and <code>Generators</code> are two
&quot;normal&quot; types you encounter in Rust. <code>Future</code>s and <code>Generator</code>s are two
exceptions.</p>
</li>
<li>
@@ -691,8 +691,8 @@ by adding <code>std::marker::PhantomPinned</code> to your type on stable.</p>
</li>
</ol>
<blockquote>
<p>Unsafe code does not mean it's literally &quot;unsafe&quot;, it only relieves the
guarantees you normally get from the compiler. An <code>unsafe</code> implementation can
<p>Unsafe code does not mean it's literally &quot;unsafe&quot;, it only relieves the
guarantees you normally get from the compiler. An <code>unsafe</code> implementation can
be perfectly safe to do, but you have no safety net.</p>
</blockquote>
<h3><a class="header" href="#projectionstructural-pinning" id="projectionstructural-pinning">Projection/structural pinning</a></h3>
@@ -705,7 +705,7 @@ for that.</p>
In the <code>Drop</code> implementation you take a mutable reference to <code>self</code>, which means
extra care must be taken when implementing <code>Drop</code> for pinned types.</p>
<h2><a class="header" href="#putting-it-all-together" id="putting-it-all-together">Putting it all together</a></h2>
<p>This is exactly what we'll do when we implement our own <code>Futures</code> stay tuned,
<p>This is exactly what we'll do when we implement our own <code>Future</code>, so stay tuned,
we're soon finished.</p>
<h2><a class="header" href="#bonus-section-fixing-our-self-referential-generator-and-learning-more-about-pin" id="bonus-section-fixing-our-self-referential-generator-and-learning-more-about-pin">Bonus section: Fixing our self-referential generator and learning more about Pin</a></h2>
<p>But now, let's prevent this problem using <code>Pin</code>. I've commented along the way to
@@ -729,7 +729,7 @@ pub fn main() {
let mut pinned1 = Box::pin(gen1);
let mut pinned2 = Box::pin(gen2);
// Uncomment these if you think it's safe to pin the values to the stack instead
// Uncomment these if you think it's safe to pin the values to the stack instead
// (it is in this case). Remember to comment out the two previous lines first.
//let mut pinned1 = unsafe { Pin::new_unchecked(&amp;mut gen1) };
//let mut pinned2 = unsafe { Pin::new_unchecked(&amp;mut gen2) };
@@ -737,7 +737,7 @@ pub fn main() {
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume() {
println!(&quot;Gen1 got value {}&quot;, n);
}
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume() {
println!(&quot;Gen2 got value {}&quot;, n);
};
@@ -752,8 +752,8 @@ pub fn main() {
}
enum GeneratorState&lt;Y, R&gt; {
Yielded(Y),
Complete(R),
Yielded(Y),
Complete(R),
}
trait Generator {
+211 -173
View File
@@ -153,7 +153,7 @@
<div id="content" class="content">
<main>
<h1><a class="header" href="#implementing-futures---main-example" id="implementing-futures---main-example">Implementing Futures - main example</a></h1>
<p>We'll create our own <code>Futures</code> together with a fake reactor and a simple
<p>We'll create our own Futures together with a fake reactor and a simple
executor which allows you to edit, run an play around with the code right here
in your browser.</p>
<p>I'll walk you through the example, but if you want to check it out closer, you
@@ -171,9 +171,9 @@ here will be in <code>main.rs</code></p>
<h2><a class="header" href="#implementing-our-own-futures" id="implementing-our-own-futures">Implementing our own Futures</a></h2>
<p>Let's start off by getting all our imports right away so you can follow along</p>
<pre><code class="language-rust noplaypen ignore">use std::{
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread::{self, JoinHandle}, time::{Duration, Instant}
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
};
</code></pre>
<h2><a class="header" href="#the-executor" id="the-executor">The Executor</a></h2>
@@ -192,8 +192,8 @@ a <code>Future</code> has resolved and should be polled again.</p>
fn block_on&lt;F: Future&gt;(mut future: F) -&gt; F::Output {
// the first thing we do is to construct a `Waker` which we'll pass on to
// the `reactor` so it can wake us up when an event is ready.
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
// the `reactor` so it can wake us up when an event is ready.
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
let waker = waker_into_waker(Arc::into_raw(mywaker));
// The context struct is just a wrapper for a `Waker` object. Maybe in the
@@ -211,7 +211,7 @@ fn block_on&lt;F: Future&gt;(mut future: F) -&gt; F::Output {
// an event occurs, or a thread has a &quot;spurious wakeup&quot; (an unexpected wakeup
// that can happen for no good reason).
let val = loop {
match Future::poll(pinned, &amp;mut cx) {
// when the Future is ready we're finished
@@ -227,23 +227,23 @@ fn block_on&lt;F: Future&gt;(mut future: F) -&gt; F::Output {
<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
here and focus only on some important aspects that might need further explanation.</p>
<p>Now that you've read so much about <code>Generators</code> 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
is a <code>yield</code> point. We could borrow data across <code>await</code> points and we meet the
exact same challenges as we do when borrowing across <code>yield</code> points.</p>
<blockquote>
<p><code>Context</code> is just a wrapper around the <code>Waker</code>. At the time of writing this
book it's nothing more. In the future it might be possible that the <code>Context</code>
object will do more than just wrapping a <code>Future</code> so having this extra
object will do more than just wrapping a <code>Future</code> so having this extra
abstraction gives some flexibility.</p>
</blockquote>
<p>As explained in the <a href="./3_generators_pin.html">chapter about generators</a>, we use
<code>Pin</code> and the guarantees that give us to allow <code>Futures</code> to have self
<code>Pin</code> and the guarantees that give us to allow <code>Future</code>s to have self
references.</p>
<h2><a class="header" href="#the-future-implementation" id="the-future-implementation">The <code>Future</code> implementation</a></h2>
<p>Futures has a well defined interface, which means they can be used across the
entire ecosystem. </p>
<p>We can chain these <code>Futures</code> so that once a <strong>leaf-future</strong> is
entire ecosystem.</p>
<p>We can chain these <code>Future</code>s so that once a <strong>leaf-future</strong> is
ready we'll perform a set of operations until either the task is finished or we
reach yet another <strong>leaf-future</strong> which we'll wait for and yield control to the
scheduler.</p>
@@ -264,9 +264,8 @@ struct MyWaker {
#[derive(Clone)]
pub struct Task {
id: usize,
reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;,
reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;,
data: u64,
is_registered: bool,
}
// These are function definitions we'll use for our waker. Remember the
@@ -306,48 +305,57 @@ fn waker_into_waker(s: *const MyWaker) -&gt; Waker {
}
impl Task {
fn new(reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;, data: u64, id: usize) -&gt; Self {
Task {
id,
reactor,
data,
is_registered: false,
}
fn new(reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;, data: u64, id: usize) -&gt; Self {
Task { id, reactor, data }
}
}
// This is our `Future` implementation
impl Future for Task {
// The output for our kind of `leaf future` is just an `usize`. For other
// futures this could be something more interesting like a byte array.
type Output = usize;
fn poll(mut self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
// Poll is the what drives the state machine forward and it's the only
// method we'll need to call to drive futures to completion.
fn poll(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
// We need to get access the reactor in our `poll` method so we acquire
// a lock on that.
let mut r = self.reactor.lock().unwrap();
// we check with the `Reactor` if this future is in its &quot;readylist&quot;
// i.e. if it's `Ready`
// First we check if the task is marked as ready
if r.is_ready(self.id) {
// if it is, we return the data. In this case it's just the ID of
// the task since this is just a very simple example.
// If it's ready we set its state to `Finished`
*r.tasks.get_mut(&amp;self.id).unwrap() = TaskState::Finished;
Poll::Ready(self.id)
} else if self.is_registered {
// If it isn't finished we check the map we have stored in our Reactor
// over id's we have registered and see if it's there
} else if r.tasks.contains_key(&amp;self.id) {
// If the future is registered alredy, we just return `Pending`
// This is important. The docs says that on multiple calls to poll,
// only the Waker from the Context passed to the most recent call
// should be scheduled to receive a wakeup. That's why we insert
// this waker into the map (which will return the old one which will
// get dropped) before we return `Pending`.
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
Poll::Pending
} else {
// If we get here, it must be the first time this `Future` is polled
// so we register a task with our `reactor`
// If it's not ready, and not in the map it's a new task so we
// register that with the Reactor and return `Pending`
r.register(self.data, cx.waker().clone(), self.id);
// oh, we have to drop the lock on our `Mutex` here because we can't
// have a shared and exclusive borrow at the same time
drop(r);
self.is_registered = true;
Poll::Pending
}
// Note that we're holding a lock on the `Mutex` which protects the
// Reactor all the way until the end of this scope. This means that
// even if our task were to complete immidiately, it will not be
// able to call `wake` while we're in our `Poll` method.
// Since we can make this guarantee, it's now the Executors job to
// handle this possible race condition where `Wake` is called after
// `poll` but before our thread goes to sleep.
}
}
</code></pre>
@@ -360,8 +368,8 @@ is pretty normal, and makes this easy and safe to work with. Cloning a <code>Wak
is just increasing the refcount in this case.</p>
<p>Dropping a <code>Waker</code> is as easy as decreasing the refcount. Now, in special
cases we could choose to not use an <code>Arc</code>. So this low-level method is there
to allow such cases. </p>
<p>Indeed, if we only used <code>Arc</code> there is no reason for us to go through all the
to allow such cases.</p>
<p>Indeed, if we only used <code>Arc</code> there is no reason for us to go through all the
trouble of creating our own <code>vtable</code> and a <code>RawWaker</code>. We could just implement
a normal trait.</p>
<p>Fortunately, in the future this will probably be possible in the standard
@@ -377,7 +385,7 @@ and call park/unpark on it.</p>
<ol>
<li>A future could call <code>unpark</code> on the executor thread from a different thread</li>
<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
<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>
@@ -402,12 +410,12 @@ to have an example to run.</p>
<p>Since concurrency mostly makes sense when interacting with the outside world (or
at least some peripheral), we need something to actually abstract over this
interaction in an asynchronous way.</p>
<p>This is the <code>Reactors</code> job. Most often you'll see reactors in Rust use a library
<p>This is the Reactors job. Most often you'll see reactors in Rust use a library
called <a href="https://github.com/tokio-rs/mio">Mio</a>, which provides non blocking APIs and event notification for
several platforms.</p>
<p>The reactor will typically give you something like a <code>TcpStream</code> (or any other
resource) which you'll use to create an I/O request. What you get in return is a
<code>Future</code>. </p>
<code>Future</code>.</p>
<blockquote>
<p>If our reactor did some real I/O work our <code>Task</code> in would instead be represent
a non-blocking <code>TcpStream</code> which registers interest with the global <code>Reactor</code>.
@@ -424,6 +432,15 @@ for the sake of this example.</p>
<p><strong>Our Reactor will look like this:</strong></p>
<pre><code class="language-rust noplaypen ignore">// This is a &quot;fake&quot; reactor. It does no real I/O, but that also makes our
// code possible to run in the book and in the playground
// The different states a task can have in this Reactor
enum TaskState {
Ready,
NotReady(Waker),
Finished,
}
// This is a &quot;fake&quot; reactor. It does no real I/O, but that also makes our
// code possible to run in the book and in the playground
struct Reactor {
// we need some way of registering a Task with the reactor. Normally this
@@ -431,106 +448,118 @@ struct Reactor {
dispatcher: Sender&lt;Event&gt;,
handle: Option&lt;JoinHandle&lt;()&gt;&gt;,
// This is a list of tasks that are ready, which means they should be polled
// for data.
readylist: Arc&lt;Mutex&lt;Vec&lt;usize&gt;&gt;&gt;,
// This is a list of tasks
tasks: HashMap&lt;usize, TaskState&gt;,
}
// We just have two kind of events. An event called `Timeout`
// and a `Close` event to close down our reactor.
// This represents the Events we can send to our reactor thread. In this
// example it's only a Timeout or a Close event.
#[derive(Debug)]
enum Event {
Close,
Timeout(Waker, u64, usize),
Timeout(u64, usize),
}
impl Reactor {
fn new() -&gt; Self {
// The way we register new events with our reactor is using a regular
// channel
// We choose to return an atomic reference counted, mutex protected, heap
// allocated `Reactor`. Just to make it easy to explain... No, the reason
// we do this is:
//
// 1. We know that only thread-safe reactors will be created.
// 2. By heap allocating it we can obtain a reference to a stable address
// that's not dependent on the stack frame of the function that called `new`
fn new() -&gt; Arc&lt;Mutex&lt;Box&lt;Self&gt;&gt;&gt; {
let (tx, rx) = channel::&lt;Event&gt;();
let readylist = Arc::new(Mutex::new(vec![]));
let rl_clone = readylist.clone();
let reactor = Arc::new(Mutex::new(Box::new(Reactor {
dispatcher: tx,
handle: None,
tasks: HashMap::new(),
})));
// Notice that we'll need to use `weak` reference here. If we don't,
// our `Reactor` will not get `dropped` when our main thread is finished
// since we're holding internal references to it.
// This `Vec` will hold handles to all the threads we spawn so we can
// join them later on and finish our programm in a good manner
let mut handles = vec![];
// Since we're collecting all `JoinHandles` from the threads we spawn
// and make sure to join them we know that `Reactor` will be alive
// longer than any reference held by the threads we spawn here.
let reactor_clone = Arc::downgrade(&amp;reactor);
// This will be the &quot;Reactor thread&quot;
// This will be our Reactor-thread. The Reactor-thread will in our case
// just spawn new threads which will serve as timers for us.
let handle = thread::spawn(move || {
let mut handles = vec![];
// This simulates some I/O resource
for event in rx {
let rl_clone = rl_clone.clone();
println!(&quot;REACTOR: {:?}&quot;, event);
let reactor = reactor_clone.clone();
match event {
// If we get a close event we break out of the loop we're in
Event::Close =&gt; break,
Event::Timeout(waker, duration, id) =&gt; {
Event::Timeout(duration, id) =&gt; {
// When we get an event we simply spawn a new thread
// which will simulate some I/O resource...
// We spawn a new thread that will serve as a timer
// and will call `wake` on the correct `Waker` once
// it's done.
let event_handle = thread::spawn(move || {
//... by sleeping for the number of seconds
// we provided when creating the `Task`.
thread::sleep(Duration::from_secs(duration));
// When it's done sleeping we put the ID of this task
// on the &quot;readylist&quot;
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
// Then we call `wake` which will wake up our
// executor and start polling the futures
waker.wake();
let reactor = reactor.upgrade().unwrap();
reactor.lock().map(|mut r| r.wake(id)).unwrap();
});
handles.push(event_handle);
}
}
}
// When we exit the Reactor we first join all the handles on
// the child threads we've spawned so we catch any panics and
// release any resources.
for handle in handles {
handle.join().unwrap();
}
// This is important for us since we need to know that these
// threads don't live longer than our Reactor-thread. Our
// Reactor-thread will be joined when `Reactor` gets dropped.
handles.into_iter().for_each(|handle| handle.join().unwrap());
});
reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
reactor
}
Reactor {
readylist,
dispatcher: tx,
handle: Some(handle),
// The wake function will call wake on the waker for the task with the
// corresponding id.
fn wake(&amp;mut self, id: usize) {
self.tasks.get_mut(&amp;id).map(|state| {
// No matter what state the task was in we can safely set it
// to ready at this point. This lets us get ownership over the
// the data that was there before we replaced it.
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();
}
// Register a new task with the reactor. In this particular example
// we panic if a task with the same id get's registered twice
fn register(&amp;mut self, duration: u64, waker: Waker, id: usize) {
if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
panic!(&quot;Tried to insert a task with id: '{}', twice!&quot;, id);
}
self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
}
fn register(&amp;mut self, duration: u64, waker: Waker, data: usize) {
// registering an event is as simple as sending an `Event` through
// the channel.
self.dispatcher
.send(Event::Timeout(waker, duration, data))
.unwrap();
}
// We send a close event to the reactor so it closes down our reactor-thread
fn close(&amp;mut self) {
self.dispatcher.send(Event::Close).unwrap();
}
// We need a way to check if any event's are ready. This will simply
// look through the &quot;readylist&quot; for an event macthing the ID we want to
// check for.
fn is_ready(&amp;self, id_to_check: usize) -&gt; bool {
self.readylist
.lock()
.map(|rl| rl.iter().any(|id| *id == id_to_check))
.unwrap()
// We simply checks if a task with this id is in the state `TaskState::Ready`
fn is_ready(&amp;self, id: usize) -&gt; bool {
self.tasks.get(&amp;id).map(|state| match state {
TaskState::Ready =&gt; true,
_ =&gt; false,
}).unwrap_or(false)
}
}
// When our `Reactor` is dropped we join the reactor thread with the thread
// owning our `Reactor` so we catch any panics and release all resources.
// It's not needed for this to work, but it really is a best practice to join
// all threads you spawn.
impl Drop for Reactor {
fn drop(&amp;mut self) {
self.handle.take().map(|h| h.join().unwrap()).unwrap();
@@ -544,9 +573,9 @@ of seconds here, just give it some time to run.</p>
<p>In the last chapter we have the <a href="./8_finished_example.html">whole 200 lines in an editable window</a>
which you can edit and change the way you like.</p>
<pre><pre class="playpen"><code class="language-rust edition2018"><span class="boring">use std::{
</span><span class="boring"> future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
</span><span class="boring"> task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
</span><span class="boring"> thread::{self, JoinHandle}, time::{Duration, Instant}
</span><span class="boring"> future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
</span><span class="boring"> task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
</span><span class="boring"> thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
</span><span class="boring">};
</span><span class="boring">
</span>fn main() {
@@ -555,10 +584,6 @@ which you can edit and change the way you like.</p>
// Many runtimes create a glocal `reactor` we pass it as an argument
let reactor = Reactor::new();
// Since we'll share this between threads we wrap it in a
// atmically-refcounted- mutex.
let reactor = Arc::new(Mutex::new(reactor));
// We create two tasks:
// - first parameter is the `reactor`
@@ -596,15 +621,18 @@ which you can edit and change the way you like.</p>
// ends nicely.
reactor.lock().map(|mut r| r.close()).unwrap();
}
<span class="boring">// ============================= EXECUTOR ====================================
</span><span class="boring">fn block_on&lt;F: Future&gt;(mut future: F) -&gt; F::Output {
</span><span class="boring"> let mywaker = Arc::new(MyWaker{ thread: thread::current() });
</span><span class="boring"> let mywaker = Arc::new(MyWaker {
</span><span class="boring"> thread: thread::current(),
</span><span class="boring"> });
</span><span class="boring"> let waker = waker_into_waker(Arc::into_raw(mywaker));
</span><span class="boring"> let mut cx = Context::from_waker(&amp;waker);
</span><span class="boring">
</span><span class="boring"> // SAFETY: we shadow `future` so it can't be accessed again.
</span><span class="boring"> let mut future = unsafe { Pin::new_unchecked(&amp;mut future) };
</span><span class="boring"> let val = loop {
</span><span class="boring"> let pinned = unsafe { Pin::new_unchecked(&amp;mut future) };
</span><span class="boring"> match Future::poll(pinned, &amp;mut cx) {
</span><span class="boring"> match Future::poll(future.as_mut(), &amp;mut cx) {
</span><span class="boring"> Poll::Ready(val) =&gt; break val,
</span><span class="boring"> Poll::Pending =&gt; thread::park(),
</span><span class="boring"> };
@@ -621,29 +649,28 @@ which you can edit and change the way you like.</p>
</span><span class="boring">#[derive(Clone)]
</span><span class="boring">pub struct Task {
</span><span class="boring"> id: usize,
</span><span class="boring"> reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;,
</span><span class="boring"> reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;,
</span><span class="boring"> data: u64,
</span><span class="boring"> is_registered: bool,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn mywaker_wake(s: &amp;MyWaker) {
</span><span class="boring"> let waker_ptr: *const MyWaker = s;
</span><span class="boring"> let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
</span><span class="boring"> let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
</span><span class="boring"> waker_arc.thread.unpark();
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">fn mywaker_clone(s: &amp;MyWaker) -&gt; RawWaker {
</span><span class="boring"> let arc = unsafe { Arc::from_raw(s).clone() };
</span><span class="boring"> let arc = unsafe { Arc::from_raw(s) };
</span><span class="boring"> std::mem::forget(arc.clone()); // increase ref count
</span><span class="boring"> RawWaker::new(Arc::into_raw(arc) as *const (), &amp;VTABLE)
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">const VTABLE: RawWakerVTable = unsafe {
</span><span class="boring"> RawWakerVTable::new(
</span><span class="boring"> |s| mywaker_clone(&amp;*(s as *const MyWaker)), // clone
</span><span class="boring"> |s| mywaker_wake(&amp;*(s as *const MyWaker)), // wake
</span><span class="boring"> |s| mywaker_wake(*(s as *const &amp;MyWaker)), // wake by ref
</span><span class="boring"> |s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
</span><span class="boring"> |s| mywaker_clone(&amp;*(s as *const MyWaker)), // clone
</span><span class="boring"> |s| mywaker_wake(&amp;*(s as *const MyWaker)), // wake
</span><span class="boring"> |s| mywaker_wake(*(s as *const &amp;MyWaker)), // wake by ref
</span><span class="boring"> |s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
</span><span class="boring"> )
</span><span class="boring">};
</span><span class="boring">
@@ -653,97 +680,109 @@ which you can edit and change the way you like.</p>
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Task {
</span><span class="boring"> fn new(reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;, data: u64, id: usize) -&gt; Self {
</span><span class="boring"> Task {
</span><span class="boring"> id,
</span><span class="boring"> reactor,
</span><span class="boring"> data,
</span><span class="boring"> is_registered: false,
</span><span class="boring"> }
</span><span class="boring"> fn new(reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;, data: u64, id: usize) -&gt; Self {
</span><span class="boring"> Task { id, reactor, data }
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Future for Task {
</span><span class="boring"> type Output = usize;
</span><span class="boring"> fn poll(mut self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
</span><span class="boring"> fn poll(self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
</span><span class="boring"> let mut r = self.reactor.lock().unwrap();
</span><span class="boring"> if r.is_ready(self.id) {
</span><span class="boring"> println!(&quot;POLL: TASK {} IS READY&quot;, self.id);
</span><span class="boring"> *r.tasks.get_mut(&amp;self.id).unwrap() = TaskState::Finished;
</span><span class="boring"> Poll::Ready(self.id)
</span><span class="boring"> } else if self.is_registered {
</span><span class="boring"> } else if r.tasks.contains_key(&amp;self.id) {
</span><span class="boring"> println!(&quot;POLL: REPLACED WAKER FOR TASK: {}&quot;, self.id);
</span><span class="boring"> r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
</span><span class="boring"> Poll::Pending
</span><span class="boring"> } else {
</span><span class="boring"> println!(&quot;POLL: REGISTERED TASK: {}, WAKER: {:?}&quot;, self.id, cx.waker());
</span><span class="boring"> r.register(self.data, cx.waker().clone(), self.id);
</span><span class="boring"> drop(r);
</span><span class="boring"> self.is_registered = true;
</span><span class="boring"> Poll::Pending
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">// =============================== REACTOR ===================================
</span><span class="boring">enum TaskState {
</span><span class="boring"> Ready,
</span><span class="boring"> NotReady(Waker),
</span><span class="boring"> Finished,
</span><span class="boring">}
</span><span class="boring">struct Reactor {
</span><span class="boring"> dispatcher: Sender&lt;Event&gt;,
</span><span class="boring"> handle: Option&lt;JoinHandle&lt;()&gt;&gt;,
</span><span class="boring"> readylist: Arc&lt;Mutex&lt;Vec&lt;usize&gt;&gt;&gt;,
</span><span class="boring"> tasks: HashMap&lt;usize, TaskState&gt;,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">#[derive(Debug)]
</span><span class="boring">enum Event {
</span><span class="boring"> Close,
</span><span class="boring"> Timeout(Waker, u64, usize),
</span><span class="boring"> Timeout(u64, usize),
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">impl Reactor {
</span><span class="boring"> fn new() -&gt; Self {
</span><span class="boring"> fn new() -&gt; Arc&lt;Mutex&lt;Box&lt;Self&gt;&gt;&gt; {
</span><span class="boring"> let (tx, rx) = channel::&lt;Event&gt;();
</span><span class="boring"> let readylist = Arc::new(Mutex::new(vec![]));
</span><span class="boring"> let rl_clone = readylist.clone();
</span><span class="boring"> let mut handles = vec![];
</span><span class="boring"> let reactor = Arc::new(Mutex::new(Box::new(Reactor {
</span><span class="boring"> dispatcher: tx,
</span><span class="boring"> handle: None,
</span><span class="boring"> tasks: HashMap::new(),
</span><span class="boring"> })));
</span><span class="boring">
</span><span class="boring"> let reactor_clone = Arc::downgrade(&amp;reactor);
</span><span class="boring"> let handle = thread::spawn(move || {
</span><span class="boring"> let mut handles = vec![];
</span><span class="boring"> // This simulates some I/O resource
</span><span class="boring"> for event in rx {
</span><span class="boring"> println!(&quot;REACTOR: {:?}&quot;, event);
</span><span class="boring"> let rl_clone = rl_clone.clone();
</span><span class="boring"> let reactor = reactor_clone.clone();
</span><span class="boring"> match event {
</span><span class="boring"> Event::Close =&gt; break,
</span><span class="boring"> Event::Timeout(waker, duration, id) =&gt; {
</span><span class="boring"> Event::Timeout(duration, id) =&gt; {
</span><span class="boring"> let event_handle = thread::spawn(move || {
</span><span class="boring"> thread::sleep(Duration::from_secs(duration));
</span><span class="boring"> rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
</span><span class="boring"> waker.wake();
</span><span class="boring"> let reactor = reactor.upgrade().unwrap();
</span><span class="boring"> reactor.lock().map(|mut r| r.wake(id)).unwrap();
</span><span class="boring"> });
</span><span class="boring">
</span><span class="boring"> handles.push(event_handle);
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> for handle in handles {
</span><span class="boring"> handle.join().unwrap();
</span><span class="boring"> }
</span><span class="boring"> handles.into_iter().for_each(|handle| handle.join().unwrap());
</span><span class="boring"> });
</span><span class="boring">
</span><span class="boring"> Reactor {
</span><span class="boring"> readylist,
</span><span class="boring"> dispatcher: tx,
</span><span class="boring"> handle: Some(handle),
</span><span class="boring"> }
</span><span class="boring"> reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
</span><span class="boring"> reactor
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn register(&amp;mut self, duration: u64, waker: Waker, data: usize) {
</span><span class="boring"> self.dispatcher
</span><span class="boring"> .send(Event::Timeout(waker, duration, data))
</span><span class="boring"> .unwrap();
</span><span class="boring"> fn wake(&amp;mut self, id: usize) {
</span><span class="boring"> self.tasks.get_mut(&amp;id).map(|state| {
</span><span class="boring"> match mem::replace(state, TaskState::Ready) {
</span><span class="boring"> TaskState::NotReady(waker) =&gt; waker.wake(),
</span><span class="boring"> TaskState::Finished =&gt; panic!(&quot;Called 'wake' twice on task: {}&quot;, id),
</span><span class="boring"> _ =&gt; unreachable!()
</span><span class="boring"> }
</span><span class="boring"> }).unwrap();
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn register(&amp;mut self, duration: u64, waker: Waker, id: usize) {
</span><span class="boring"> if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
</span><span class="boring"> panic!(&quot;Tried to insert a task with id: '{}', twice!&quot;, id);
</span><span class="boring"> }
</span><span class="boring"> self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn close(&amp;mut self) {
</span><span class="boring"> self.dispatcher.send(Event::Close).unwrap();
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> fn is_ready(&amp;self, id_to_check: usize) -&gt; bool {
</span><span class="boring"> self.readylist
</span><span class="boring"> .lock()
</span><span class="boring"> .map(|rl| rl.iter().any(|id| *id == id_to_check))
</span><span class="boring"> .unwrap()
</span><span class="boring"> fn is_ready(&amp;self, id: usize) -&gt; bool {
</span><span class="boring"> self.tasks.get(&amp;id).map(|state| match state {
</span><span class="boring"> TaskState::Ready =&gt; true,
</span><span class="boring"> _ =&gt; false,
</span><span class="boring"> }).unwrap_or(false)
</span><span class="boring"> }
</span><span class="boring">}
</span><span class="boring">
@@ -753,18 +792,17 @@ which you can edit and change the way you like.</p>
</span><span class="boring"> }
</span><span class="boring">}
</span></code></pre></pre>
<p>I added a debug printout of the events the reactor registered interest for so we can observe
two things:</p>
<p>I added a some debug printouts so we can observe a couple of things:</p>
<ol>
<li>How the <code>Waker</code> object looks just like the <em>trait object</em> we talked about in an earlier chapter</li>
<li>In what order the events register interest with the reactor</li>
<li>The program flow from start to finish</li>
</ol>
<p>The last point is relevant when we move on the the last paragraph.</p>
<h2><a class="header" href="#asyncawait-and-concurrecy" id="asyncawait-and-concurrecy">Async/Await and concurrecy</a></h2>
<p>The <code>async</code> keyword can be used on functions as in <code>async fn(...)</code> or on a
block as in <code>async { ... }</code>. Both will turn your function, or block, into a
<code>Future</code>.</p>
<p>These <code>Futures</code> are rather simple. Imagine our generator from a few chapters
<p>These Futures are rather simple. Imagine our generator from a few chapters
back. Every <code>await</code> point is like a <code>yield</code> point.</p>
<p>Instead of <code>yielding</code> a value we pass in, we yield the result of calling <code>poll</code> on
the next <code>Future</code> we're awaiting.</p>
@@ -778,7 +816,7 @@ to <code>spawn</code> them so the executor starts running them concurrently.</p>
<pre><code class="language-ignore">Future got 1 at time: 1.00.
Future got 2 at time: 3.00.
</code></pre>
<p>If these <code>Futures</code> were executed asynchronously we would expect to see:</p>
<p>If these Futures were executed asynchronously we would expect to see:</p>
<pre><code class="language-ignore">Future got 1 at time: 1.00.
Future got 2 at time: 2.00.
</code></pre>
@@ -795,7 +833,7 @@ the concept of Futures by now helping you along the way.</p>
how they implement different ways of running Futures to completion.</p>
<p><a href="./conclusion.html#building-a-better-exectuor">If I were you I would read this next, and try to implement it for our example.</a>.</p>
<p>That's actually it for now. There as probably much more to learn, this is enough
for today. </p>
for today.</p>
<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>
<p>Don't forget the exercises in the last chapter 😊.</p>
+68 -59
View File
@@ -155,16 +155,9 @@
<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
run it yourself. Have fun!</p>
<pre><pre class="playpen"><code class="language-rust editable edition2018">use std::{
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread::{self, JoinHandle}, time::{Duration, Instant}
};
fn main() {
<pre><pre class="playpen"><code class="language-rust editable edition2018">fn main() {
let start = Instant::now();
let reactor = Reactor::new();
let reactor = Arc::new(Mutex::new(reactor));
let future1 = Task::new(reactor.clone(), 1, 1);
let future2 = Task::new(reactor.clone(), 2, 2);
@@ -188,10 +181,17 @@ fn main() {
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,
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 mywaker = Arc::new(MyWaker {
thread: thread::current(),
});
let waker = waker_into_waker(Arc::into_raw(mywaker));
let mut cx = Context::from_waker(&amp;waker);
@@ -215,14 +215,13 @@ struct MyWaker {
#[derive(Clone)]
pub struct Task {
id: usize,
reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;,
reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;,
data: u64,
is_registered: bool,
}
fn mywaker_wake(s: &amp;MyWaker) {
let waker_ptr: *const MyWaker = s;
let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
waker_arc.thread.unpark();
}
@@ -234,10 +233,10 @@ fn mywaker_clone(s: &amp;MyWaker) -&gt; RawWaker {
const VTABLE: RawWakerVTable = unsafe {
RawWakerVTable::new(
|s| mywaker_clone(&amp;*(s as *const MyWaker)), // clone
|s| mywaker_wake(&amp;*(s as *const MyWaker)), // wake
|s| mywaker_wake(*(s as *const &amp;MyWaker)), // wake by ref
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
|s| mywaker_clone(&amp;*(s as *const MyWaker)), // clone
|s| mywaker_wake(&amp;*(s as *const MyWaker)), // wake
|s| mywaker_wake(*(s as *const &amp;MyWaker)), // wake by ref
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
)
};
@@ -247,97 +246,107 @@ fn waker_into_waker(s: *const MyWaker) -&gt; Waker {
}
impl Task {
fn new(reactor: Arc&lt;Mutex&lt;Reactor&gt;&gt;, data: u64, id: usize) -&gt; Self {
Task {
id,
reactor,
data,
is_registered: false,
}
fn new(reactor: Arc&lt;Mutex&lt;Box&lt;Reactor&gt;&gt;&gt;, data: u64, id: usize) -&gt; Self {
Task { id, reactor, data }
}
}
impl Future for Task {
type Output = usize;
fn poll(mut self: Pin&lt;&amp;mut Self&gt;, cx: &amp;mut Context&lt;'_&gt;) -&gt; Poll&lt;Self::Output&gt; {
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 self.is_registered {
} 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);
drop(r);
self.is_registered = true;
Poll::Pending
}
}
}
// =============================== REACTOR ===================================
enum TaskState {
Ready,
NotReady(Waker),
Finished,
}
struct Reactor {
dispatcher: Sender&lt;Event&gt;,
handle: Option&lt;JoinHandle&lt;()&gt;&gt;,
readylist: Arc&lt;Mutex&lt;Vec&lt;usize&gt;&gt;&gt;,
tasks: HashMap&lt;usize, TaskState&gt;,
}
#[derive(Debug)]
enum Event {
Close,
Timeout(Waker, u64, usize),
Timeout(u64, usize),
}
impl Reactor {
fn new() -&gt; Self {
fn new() -&gt; Arc&lt;Mutex&lt;Box&lt;Self&gt;&gt;&gt; {
let (tx, rx) = channel::&lt;Event&gt;();
let readylist = Arc::new(Mutex::new(vec![]));
let rl_clone = readylist.clone();
let mut handles = vec![];
let reactor = Arc::new(Mutex::new(Box::new(Reactor {
dispatcher: tx,
handle: None,
tasks: HashMap::new(),
})));
let reactor_clone = Arc::downgrade(&amp;reactor);
let handle = thread::spawn(move || {
// This simulates some I/O resource
let mut handles = vec![];
for event in rx {
println!(&quot;REACTOR: {:?}&quot;, event);
let rl_clone = rl_clone.clone();
let reactor = reactor_clone.clone();
match event {
Event::Close =&gt; break,
Event::Timeout(waker, duration, id) =&gt; {
Event::Timeout(duration, id) =&gt; {
let event_handle = thread::spawn(move || {
thread::sleep(Duration::from_secs(duration));
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
waker.wake();
let reactor = reactor.upgrade().unwrap();
reactor.lock().map(|mut r| r.wake(id)).unwrap();
});
handles.push(event_handle);
}
}
}
for handle in handles {
handle.join().unwrap();
}
handles.into_iter().for_each(|handle| handle.join().unwrap());
});
Reactor {
readylist,
dispatcher: tx,
handle: Some(handle),
}
reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
reactor
}
fn register(&amp;mut self, duration: u64, waker: Waker, data: usize) {
self.dispatcher
.send(Event::Timeout(waker, duration, data))
.unwrap();
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();
}
fn register(&amp;mut self, duration: u64, waker: Waker, id: usize) {
if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
panic!(&quot;Tried to insert a task with id: '{}', twice!&quot;, id);
}
self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
}
fn close(&amp;mut self) {
self.dispatcher.send(Event::Close).unwrap();
}
fn is_ready(&amp;self, id_to_check: usize) -&gt; bool {
self.readylist
.lock()
.map(|rl| rl.iter().any(|id| *id == id_to_check))
.unwrap()
fn is_ready(&amp;self, id: usize) -&gt; bool {
self.tasks.get(&amp;id).map(|state| match state {
TaskState::Ready =&gt; true,
_ =&gt; false,
}).unwrap_or(false)
}
}
+4 -4
View File
@@ -153,7 +153,7 @@
<div id="content" class="content">
<main>
<h1><a class="header" href="#futures-explained-in-200-lines-of-rust" id="futures-explained-in-200-lines-of-rust">Futures Explained in 200 Lines of Rust</a></h1>
<p>This book aims to explain <code>Futures</code> in Rust using an example driven approach,
<p>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
in programming.</p>
@@ -178,7 +178,7 @@ about runtimes and how they work, especially:</p>
<p>I've limited myself to a 200 line main example (hence the title) to limit the
scope and introduce an example that can easily be explored further.</p>
<p>However, there is a lot to digest and it's not what I would call easy, but we'll
take everything step by step so get a cup of tea and relax. </p>
take everything step by step so get a cup of tea and relax.</p>
<p>I hope you enjoy the ride.</p>
<blockquote>
<p>This book is developed in the open, and contributions are welcome. You'll find
@@ -199,8 +199,8 @@ in Rust. If you like it, you might want to check out the others as well:</p>
</ul>
<h2><a class="header" href="#credits-and-thanks" id="credits-and-thanks">Credits and thanks</a></h2>
<p>I'd like to take this chance to thank the people behind <code>mio</code>, <code>tokio</code>,
<code>async_std</code>, <code>Futures</code>, <code>libc</code>, <code>crossbeam</code> which underpins so much of the
async ecosystem and rarely gets enough praise in my eyes.</p>
<code>async_std</code>, <code>futures</code>, <code>libc</code>, <code>crossbeam</code> which underpins so much of the
async ecosystem and and rarely gets enough praise in my eyes.</p>
<p>A special thanks to <a href="https://twitter.com/jonhoo">jonhoo</a> who was kind enough to
give me some valuable feedback on a very early draft of this book. He has not
read the finished product, but a big thanks is definitely due.</p>
+4 -4
View File
@@ -153,7 +153,7 @@
<div id="content" class="content">
<main>
<h1><a class="header" href="#futures-explained-in-200-lines-of-rust" id="futures-explained-in-200-lines-of-rust">Futures Explained in 200 Lines of Rust</a></h1>
<p>This book aims to explain <code>Futures</code> in Rust using an example driven approach,
<p>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
in programming.</p>
@@ -178,7 +178,7 @@ about runtimes and how they work, especially:</p>
<p>I've limited myself to a 200 line main example (hence the title) to limit the
scope and introduce an example that can easily be explored further.</p>
<p>However, there is a lot to digest and it's not what I would call easy, but we'll
take everything step by step so get a cup of tea and relax. </p>
take everything step by step so get a cup of tea and relax.</p>
<p>I hope you enjoy the ride.</p>
<blockquote>
<p>This book is developed in the open, and contributions are welcome. You'll find
@@ -199,8 +199,8 @@ in Rust. If you like it, you might want to check out the others as well:</p>
</ul>
<h2><a class="header" href="#credits-and-thanks" id="credits-and-thanks">Credits and thanks</a></h2>
<p>I'd like to take this chance to thank the people behind <code>mio</code>, <code>tokio</code>,
<code>async_std</code>, <code>Futures</code>, <code>libc</code>, <code>crossbeam</code> which underpins so much of the
async ecosystem and rarely gets enough praise in my eyes.</p>
<code>async_std</code>, <code>futures</code>, <code>libc</code>, <code>crossbeam</code> which underpins so much of the
async ecosystem and and rarely gets enough praise in my eyes.</p>
<p>A special thanks to <a href="https://twitter.com/jonhoo">jonhoo</a> who was kind enough to
give me some valuable feedback on a very early draft of this book. He has not
read the finished product, but a big thanks is definitely due.</p>
+362 -314
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-3
View File
@@ -60,6 +60,3 @@ Before we go on further, let's separate the topic of async programming into some
#### How \`Futures\` are implemented in the language
If you've followed the discussions about Rusts `Futures` and `async/await` you realize that there has gone a ton of work into implementing these concepts in the Runtime.
+35 -34
View File
@@ -72,12 +72,12 @@ First, for computers to be [_efficient_](https://en.wikipedia.org/wiki/Efficienc
start to look under the covers (like [how an operating system works](https://os.phil-opp.com/async-await/))
you'll see concurrency everywhere. It's very fundamental in everything we do.
Second, we have the web.
Secondly, we have the web.
Web servers are all about I/O and handling small tasks
(requests). When the number of small tasks is large it's not a good fit for OS
threads as of today because of the memory they require and the overhead involved
when creating new threads.
when creating new threads.
This gets even more problematic when the load is variable which means the current number of tasks a
program has at any point in time is unpredictable. That's why you'll see so many async web
@@ -102,7 +102,7 @@ such a system) which then continues running a different task.
Rust had green threads once, but they were removed before it hit 1.0. The state
of execution is stored in each stack so in such a solution there would be no
need for `async`, `await`, `Futures` or `Pin`.
need for `async`, `await`, `Future` or `Pin`.
**The typical flow looks like this:**
@@ -145,27 +145,28 @@ A green threads example could look something like this:
> It's not in any way meant to showcase "best practice". Just so we're on
> the same page.
_**Press the expand icon in the top right corner to show the example code.**_
_**Press the expand icon in the top right corner to show the example code.**_
```rust, edition2018
# #![feature(asm, naked_functions)]
# use std::ptr;
#
#
# const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
# const MAX_THREADS: usize = 4;
# static mut RUNTIME: usize = 0;
#
#
# pub struct Runtime {
# threads: Vec<Thread>,
# current: usize,
# }
#
#
# #[derive(PartialEq, Eq, Debug)]
# enum State {
# Available,
# Running,
# Ready,
# }
#
#
# struct Thread {
# id: usize,
# stack: Vec<u8>,
@@ -173,7 +174,7 @@ _**Press the expand icon in the top right corner to show the example code.**_
# state: State,
# task: Option<Box<dyn Fn()>>,
# }
#
#
# #[derive(Debug, Default)]
# #[repr(C)]
# struct ThreadContext {
@@ -186,7 +187,7 @@ _**Press the expand icon in the top right corner to show the example code.**_
# rbp: u64,
# thread_ptr: u64,
# }
#
#
# impl Thread {
# fn new(id: usize) -> Self {
# Thread {
@@ -198,7 +199,7 @@ _**Press the expand icon in the top right corner to show the example code.**_
# }
# }
# }
#
#
# impl Runtime {
# pub fn new() -> Self {
# let base_thread = Thread {
@@ -208,37 +209,37 @@ _**Press the expand icon in the top right corner to show the example code.**_
# state: State::Running,
# task: None,
# };
#
#
# let mut threads = vec![base_thread];
# threads[0].ctx.thread_ptr = &threads[0] as *const Thread as u64;
# let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
# threads.append(&mut available_threads);
#
#
# Runtime {
# threads,
# current: 0,
# }
# }
#
#
# pub fn init(&self) {
# unsafe {
# let r_ptr: *const Runtime = self;
# RUNTIME = r_ptr as usize;
# }
# }
#
#
# pub fn run(&mut self) -> ! {
# while self.t_yield() {}
# std::process::exit(0);
# }
#
#
# fn t_return(&mut self) {
# if self.current != 0 {
# self.threads[self.current].state = State::Available;
# self.t_yield();
# }
# }
#
#
# fn t_yield(&mut self) -> bool {
# let mut pos = self.current;
# while self.threads[pos].state != State::Ready {
@@ -250,21 +251,21 @@ _**Press the expand icon in the top right corner to show the example code.**_
# return false;
# }
# }
#
#
# if self.threads[self.current].state != State::Available {
# self.threads[self.current].state = State::Ready;
# }
#
#
# self.threads[pos].state = State::Running;
# let old_pos = self.current;
# self.current = pos;
#
#
# unsafe {
# switch(&mut self.threads[old_pos].ctx, &self.threads[pos].ctx);
# }
# true
# }
#
#
# pub fn spawn<F: Fn() + 'static>(f: F){
# unsafe {
# let rt_ptr = RUNTIME as *mut Runtime;
@@ -273,7 +274,7 @@ _**Press the expand icon in the top right corner to show the example code.**_
# .iter_mut()
# .find(|t| t.state == State::Available)
# .expect("no available thread.");
#
#
# let size = available.stack.len();
# let s_ptr = available.stack.as_mut_ptr();
# available.task = Some(Box::new(f));
@@ -285,14 +286,14 @@ _**Press the expand icon in the top right corner to show the example code.**_
# }
# }
# }
#
#
# fn call(thread: u64) {
# let thread = unsafe { &*(thread as *const Thread) };
# if let Some(f) = &thread.task {
# f();
# }
# }
#
#
# #[naked]
# fn guard() {
# unsafe {
@@ -302,14 +303,14 @@ _**Press the expand icon in the top right corner to show the example code.**_
# rt.t_return();
# };
# }
#
#
# pub fn yield_thread() {
# unsafe {
# let rt_ptr = RUNTIME as *mut Runtime;
# (*rt_ptr).t_yield();
# };
# }
#
#
# #[naked]
# #[inline(never)]
# unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
@@ -321,7 +322,7 @@ _**Press the expand icon in the top right corner to show the example code.**_
# mov %r12, 0x20($0)
# mov %rbx, 0x28($0)
# mov %rbp, 0x30($0)
#
#
# mov 0x00($1), %rsp
# mov 0x08($1), %r15
# mov 0x10($1), %r14
@@ -366,7 +367,7 @@ the same. You can always go back and read the book which explains it later.
## Callback based approaches
You probably already know what we're going to talk about in the next paragraphs
from JavaScript which I assume most know.
from JavaScript which I assume most know.
>If your exposure to JavaScript callbacks has given you any sorts of PTSD earlier
in life, close your eyes now and scroll down for 2-3 seconds. You'll find a link
@@ -482,8 +483,8 @@ as timers but could represent any kind of resource that we'll have to wait for.
You might start to wonder by now, when are we going to talk about Futures?
Well, we're getting there. You see `promises`, `futures` and other names for
deferred computations are often used interchangeably.
Well, we're getting there. You see Promises, Futures and other names for
deferred computations are often used interchangeably.
There are formal differences between them, but we won't cover those
here. It's worth explaining `promises` a bit since they're widely known due to
@@ -521,8 +522,8 @@ timer(200)
```
The change is even more substantial under the hood. You see, promises return
a state machine which can be in one of three states: `pending`, `fulfilled` or
`rejected`.
a state machine which can be in one of three states: `pending`, `fulfilled` or
`rejected`.
When we call `timer(200)` in the sample above, we get back a promise in the state `pending`.
@@ -558,6 +559,6 @@ get into the right mindset for exploring Rust's Futures.
> need to be polled once before they do any work.
<br />
<div style="text-align: center; padding-top: 2em;">
<div style="text-align: center; padding-top: 2em;">
<a href="/books-futures-explained/1_futures_in_rust.html" style="background: red; color: white; padding:2em 2em 2em 2em; font-size: 1.2em;"><strong>PANIC BUTTON (next chapter)</strong></a>
</div>
</div>
+10 -10
View File
@@ -91,7 +91,7 @@ Rust is different from these languages in the sense that Rust doesn't come with
a runtime for handling concurrency, so you need to use a library which provide
this for you.
Quite a bit of complexity attributed to `Futures` is actually complexity rooted
Quite a bit of complexity attributed to Futures is actually complexity rooted
in runtimes. Creating an efficient runtime is hard.
Learning how to use one correctly requires quite a bit of effort as well, but
@@ -114,7 +114,7 @@ on the `Future`.
You can think of the former as the reactor's job, and the latter as the
executors job. These two parts of a runtime interact with each other using the `Waker` type.
The two most popular runtimes for `Futures` as of writing this is:
The two most popular runtimes for Futures as of writing this is:
- [async-std](https://github.com/async-rs/async-std)
- [Tokio](https://github.com/tokio-rs/tokio)
@@ -138,17 +138,17 @@ take a look at this async block using pseudo-rust as example:
```rust, ignore
let non_leaf = async {
let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap(); // <-- yield
// request a large dataset
let result = stream.write(get_dataset_request).await.unwrap(); // <-- yield
// wait for the dataset
let mut response = vec![];
stream.read(&mut response).await.unwrap(); // <-- yield
// do some CPU-intensive analysis on the dataset
let report = analyzer::analyze_data(response).unwrap();
// send the results back
stream.write(report).await.unwrap(); // <-- yield
};
@@ -189,16 +189,16 @@ can either perform CPU-intensive tasks or "blocking" tasks which is not supporte
by the runtime.
Now, armed with this knowledge you are already on a good way for understanding
Futures, but we're not gonna stop yet, there is lots of details to cover.
Futures, but we're not gonna stop yet, there is lots of details to cover.
Take a break or a cup of coffe and get ready as we go for a deep dive in the next chapters.
## Bonus section
If you find the concepts of concurrency and async programming confusing in
general, I know where you're coming from and I have written some resources to
try to give a high level overview that will make it easier to learn Rusts
`Futures` afterwards:
general, I know where you're coming from and I have written some resources to
try to give a high level overview that will make it easier to learn Rusts
Futures afterwards:
* [Async Basics - The difference between concurrency and parallelism](https://cfsamson.github.io/book-exploring-async-basics/1_concurrent_vs_parallel.html)
* [Async Basics - Async history](https://cfsamson.github.io/book-exploring-async-basics/2_async_history.html)
@@ -206,7 +206,7 @@ try to give a high level overview that will make it easier to learn Rusts
* [Async Basics - Epoll, Kqueue and IOCP](https://cfsamson.github.io/book-exploring-async-basics/6_epoll_kqueue_iocp.html)
Learning these concepts by studying futures is making it much harder than
it needs to be, so go on and read these chapters if you feel a bit unsure.
it needs to be, so go on and read these chapters if you feel a bit unsure.
I'll be right here when you're back.
+4 -4
View File
@@ -32,12 +32,12 @@ task-local storage and provide space for debugging hooks in later iterations.
## Understanding the `Waker`
One of the most confusing things we encounter when implementing our own `Futures`
One of the most confusing things we encounter when implementing our own `Future`s
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 our selves.
>If you want to know more about dynamic dispatch in Rust I can recommend an
>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/).
Let's explain this a bit more in detail.
@@ -46,7 +46,7 @@ Let's explain this a bit more in detail.
To get a better understanding of how we implement the `Waker` in Rust, we need
to take a step back and talk about some fundamentals. Let's start by taking a
look at the size of some different pointer types in Rust.
look at the size of some different pointer types in Rust.
Run the following code _(You'll have to press "play" to see the output)_:
@@ -177,4 +177,4 @@ use purely global functions and state, or any other way you wish.
This leaves a lot of options on the table for runtime implementors.
[rfc2592]:https://github.com/rust-lang/rfcs/blob/master/text/2592-futures.md#waking-up
[rfc2592]:https://github.com/rust-lang/rfcs/blob/master/text/2592-futures.md#waking-up
+31 -30
View File
@@ -6,7 +6,7 @@
>- See first hand why we need `Pin`
>- Understand what makes Rusts async model very memory efficient
>
>The motivation for `Generators` can be found in [RFC#2033][rfc2033]. It's very
>The motivation for `Generator`s can be found in [RFC#2033][rfc2033]. It's very
>well written and I can recommend reading through it (it talks as much about
>async/await as it does about generators).
@@ -38,7 +38,7 @@ coroutines which Rust uses today.
### Combinators
`Futures 0.1` used combinators. If you've worked with `Promises` in JavaScript,
`Futures 0.1` used combinators. If you've worked with Promises in JavaScript,
you already know combinators. In Rust they look like this:
```rust,noplaypen,ignore
@@ -93,7 +93,7 @@ async fn myfn() {
Async in Rust is implemented using Generators. So to understand how async really
works we need to understand generators first. Generators in Rust are implemented
as state machines.
as state machines.
The memory footprint of a chain of computations is defined by _the largest footprint
that a single step requires_.
@@ -206,7 +206,7 @@ impl Generator for GeneratorA {
Now that you know that the `yield` keyword in reality rewrites your code to become a state machine,
you'll also know the basics of how `await` works. It's very similar.
Now, there are some limitations in our naive state machine above. What happens when you have a
Now, there are some limitations in our naive state machine above. What happens when you have a
`borrow` across a `yield` point?
We could forbid that, but **one of the major design goals for the async/await syntax has been
@@ -247,10 +247,10 @@ Now what does our rewritten state machine look like with this example?
```rust,compile_fail
# enum GeneratorState<Y, R> {
# Yielded(Y),
# Yielded(Y),
# Complete(R),
# }
#
#
# trait Generator {
# type Yield;
# type Return;
@@ -314,8 +314,8 @@ As you'll notice, this compiles just fine!
```rust
enum GeneratorState<Y, R> {
Yielded(Y),
Complete(R),
Yielded(Y),
Complete(R),
}
trait Generator {
@@ -348,12 +348,12 @@ impl Generator for GeneratorA {
let borrowed = &to_borrow;
let res = borrowed.len();
*self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
// NB! And we set the pointer to reference the to_borrow string here
if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
*borrowed = to_borrow;
}
GeneratorState::Yielded(res)
}
@@ -401,16 +401,16 @@ pub fn main() {
};
}
# enum GeneratorState<Y, R> {
# Yielded(Y),
# Complete(R),
# Yielded(Y),
# Complete(R),
# }
#
#
# trait Generator {
# type Yield;
# type Return;
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
# }
#
#
# enum GeneratorA {
# Enter,
# Yield1 {
@@ -419,7 +419,7 @@ pub fn main() {
# },
# Exit,
# }
#
#
# impl GeneratorA {
# fn start() -> Self {
# GeneratorA::Enter
@@ -435,15 +435,15 @@ pub fn main() {
# let borrowed = &to_borrow;
# let res = borrowed.len();
# *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
#
#
# // We set the self-reference here
# if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
# *borrowed = to_borrow;
# }
#
#
# GeneratorState::Yielded(res)
# }
#
#
# GeneratorA::Yield1 {borrowed, ..} => {
# let borrowed: &String = unsafe {&**borrowed};
# println!("{} world", borrowed);
@@ -459,6 +459,7 @@ pub fn main() {
The problem is that in safe Rust we can still do this:
_Run the code and compare the results. Do you see the problem?_
```rust, should_panic
# #![feature(never_type)] // Force nightly compiler to be used in playground
# // by betting on it's true that this type is named after it's stabilization date...
@@ -482,16 +483,16 @@ pub fn main() {
};
}
# enum GeneratorState<Y, R> {
# Yielded(Y),
# Complete(R),
# Yielded(Y),
# Complete(R),
# }
#
#
# trait Generator {
# type Yield;
# type Return;
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
# }
#
#
# enum GeneratorA {
# Enter,
# Yield1 {
@@ -500,7 +501,7 @@ pub fn main() {
# },
# Exit,
# }
#
#
# impl GeneratorA {
# fn start() -> Self {
# GeneratorA::Enter
@@ -516,15 +517,15 @@ pub fn main() {
# let borrowed = &to_borrow;
# let res = borrowed.len();
# *self = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};
#
#
# // We set the self-reference here
# if let GeneratorA::Yield1 {to_borrow, borrowed} = self {
# *borrowed = to_borrow;
# }
#
#
# GeneratorState::Yielded(res)
# }
#
#
# GeneratorA::Yield1 {borrowed, ..} => {
# let borrowed: &String = unsafe {&**borrowed};
# println!("{} world", borrowed);
@@ -545,7 +546,7 @@ while using just safe Rust. This is a big problem!
> I've actually forced the code above to use the nightly version of the compiler.
> If you run [the example above on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5cbe9897c0e23a502afd2740c7e78b98),
> you'll see that it runs without panicing on the current stable (1.42.0) but
> you'll see that it runs without panicking on the current stable (1.42.0) but
> panics on the current nightly (1.44.0). Scary!
We'll explain exactly what happened here using a slightly simpler example in the next
@@ -625,7 +626,7 @@ pub fn main() {
yield borrowed.len();
println!("{} world!", borrowed);
};
let gen2 = static || {
let to_borrow = String::from("Hello");
let borrowed = &to_borrow;
@@ -639,7 +640,7 @@ pub fn main() {
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume(()) {
println!("Gen1 got value {}", n);
}
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume(()) {
println!("Gen2 got value {}", n);
};
@@ -655,4 +656,4 @@ pub fn main() {
[rfc1832]: https://github.com/rust-lang/rfcs/pull/1832
[optimizing-await]: https://tmandry.gitlab.io/blog/posts/optimizing-await-1/
[pr45337]: https://github.com/rust-lang/rust/pull/45337/files
[issue43122]: https://github.com/rust-lang/rust/issues/43122
[issue43122]: https://github.com/rust-lang/rust/issues/43122
+50 -50
View File
@@ -30,7 +30,7 @@ Yep, you're right, that's double negation right there. `!Unpin` means
On a more serious note, I feel obliged to mention that there are valid reasons
for the names that were chosen. Naming is not easy, and I considered renaming
`Unpin` and `!Unpin` in this book to make them easier to reason about.
`Unpin` and `!Unpin` in this book to make them easier to reason about.
However, an experienced member of the Rust community convinced me that that there
is just too many nuances and edge-cases to consider which is easily overlooked when
@@ -71,11 +71,11 @@ impl Test {
let self_ref: *const String = &self.a;
self.b = self_ref;
}
fn a(&self) -> &str {
&self.a
}
}
fn b(&self) -> &String {
unsafe {&*(self.b)}
}
@@ -112,7 +112,7 @@ fn main() {
# a: String,
# b: *const String,
# }
#
#
# impl Test {
# fn new(txt: &str) -> Self {
# let a = String::from(txt);
@@ -121,17 +121,17 @@ fn main() {
# b: std::ptr::null(),
# }
# }
#
#
# // We need an `init` method to actually set our self-reference
# fn init(&mut self) {
# let self_ref: *const String = &self.a;
# self.b = self_ref;
# }
#
#
# fn a(&self) -> &str {
# &self.a
# }
#
# }
#
# fn b(&self) -> &String {
# unsafe {&*(self.b)}
# }
@@ -168,7 +168,7 @@ fn main() {
# a: String,
# b: *const String,
# }
#
#
# impl Test {
# fn new(txt: &str) -> Self {
# let a = String::from(txt);
@@ -177,16 +177,16 @@ fn main() {
# b: std::ptr::null(),
# }
# }
#
#
# fn init(&mut self) {
# let self_ref: *const String = &self.a;
# self.b = self_ref;
# }
#
#
# fn a(&self) -> &str {
# &self.a
# }
#
# }
#
# fn b(&self) -> &String {
# unsafe {&*(self.b)}
# }
@@ -234,7 +234,7 @@ fn main() {
# a: String,
# b: *const String,
# }
#
#
# impl Test {
# fn new(txt: &str) -> Self {
# let a = String::from(txt);
@@ -243,16 +243,16 @@ fn main() {
# b: std::ptr::null(),
# }
# }
#
#
# fn init(&mut self) {
# let self_ref: *const String = &self.a;
# self.b = self_ref;
# }
#
#
# fn a(&self) -> &str {
# &self.a
# }
#
# }
#
# fn b(&self) -> &String {
# unsafe {&*(self.b)}
# }
@@ -267,7 +267,7 @@ I created a diagram to help visualize what's going on:
**Fig 1: Before and after swap**
![swap_problem](./assets/swap_problem.jpg)
As you can see this results in unwanted behavior. It's easy to get this to
As you can see this results in unwanted behavior. It's easy to get this to
segfault, show UB and fail in other spectacular ways as well.
## Pinning to the stack
@@ -329,7 +329,7 @@ pub fn main() {
// Notice how we shadow `test1` to prevent it from beeing accessed again
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new("test2");
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
Test::init(test2.as_mut());
@@ -339,15 +339,15 @@ pub fn main() {
}
# use std::pin::Pin;
# use std::marker::PhantomPinned;
#
#
# #[derive(Debug)]
# struct Test {
# a: String,
# b: *const String,
# _marker: PhantomPinned,
# }
#
#
#
#
# impl Test {
# fn new(txt: &str) -> Self {
# let a = String::from(txt);
@@ -363,11 +363,11 @@ pub fn main() {
# let this = unsafe { self.get_unchecked_mut() };
# this.b = self_ptr;
# }
#
#
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
# &self.get_ref().a
# }
#
#
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
# unsafe { &*(self.b) }
# }
@@ -382,26 +382,26 @@ pub fn main() {
let mut test1 = Test::new("test1");
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new("test2");
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
Test::init(test2.as_mut());
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
std::mem::swap(test1.as_mut(), test2.as_mut());
std::mem::swap(test1.get_mut(), test2.get_mut());
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}
# use std::pin::Pin;
# use std::marker::PhantomPinned;
#
#
# #[derive(Debug)]
# struct Test {
# a: String,
# b: *const String,
# _marker: PhantomPinned,
# }
#
#
#
#
# impl Test {
# fn new(txt: &str) -> Self {
# let a = String::from(txt);
@@ -417,11 +417,11 @@ pub fn main() {
# let this = unsafe { self.get_unchecked_mut() };
# this.b = self_ptr;
# }
#
#
# fn a<'a>(self: Pin<&'a Self>) -> &'a str {
# &self.get_ref().a
# }
#
#
# fn b<'a>(self: Pin<&'a Self>) -> &'a String {
# unsafe { &*(self.b) }
# }
@@ -434,9 +434,9 @@ us from swapping the pinned pointers.
> It's important to note that stack pinning will always depend on the current
> stack frame we're in, so we can't create a self referential object in one
> stack frame and return it since any pointers we take to "self" is invalidated.
>
>
> It also puts a lot of responsibility in your hands if you pin a value to the
> stack. A mistake that is easy to make is, forgetting to shadow the original variable
> stack. A mistake that is easy to make is, forgetting to shadow the original variable
> since you could drop the pinned pointer and access the old value
> after it's initialized like this:
>
@@ -446,7 +446,7 @@ us from swapping the pinned pointers.
> let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
> Test::init(test1_pin.as_mut());
> drop(test1_pin);
>
>
> let mut test2 = Test::new("test2");
> mem::swap(&mut test1, &mut test2);
> println!("Not self referential anymore: {:?}", test1.b);
@@ -454,15 +454,15 @@ us from swapping the pinned pointers.
> # use std::pin::Pin;
> # use std::marker::PhantomPinned;
> # use std::mem;
> #
> #
> # #[derive(Debug)]
> # struct Test {
> # a: String,
> # b: *const String,
> # _marker: PhantomPinned,
> # }
> #
> #
> #
> #
> # impl Test {
> # fn new(txt: &str) -> Self {
> # let a = String::from(txt);
@@ -478,11 +478,11 @@ us from swapping the pinned pointers.
> # let this = unsafe { self.get_unchecked_mut() };
> # this.b = self_ptr;
> # }
> #
> #
> # fn a<'a>(self: Pin<&'a Self>) -> &'a str {
> # &self.get_ref().a
> # }
> #
> #
> # fn b<'a>(self: Pin<&'a Self>) -> &'a String {
> # unsafe { &*(self.b) }
> # }
@@ -564,7 +564,7 @@ code.
certain operations on this value.
1. Most standard library types implement `Unpin`. The same goes for most
"normal" types you encounter in Rust. `Futures` and `Generators` are two
"normal" types you encounter in Rust. `Future`s and `Generator`s are two
exceptions.
5. The main use case for `Pin` is to allow self referential types, the whole
@@ -585,8 +585,8 @@ by adding `std::marker::PhantomPinned` to your type on stable.
10. Pinning a `!Unpin` pointer to the heap does not require `unsafe`. There is a shortcut for doing this using `Box::pin`.
> Unsafe code does not mean it's literally "unsafe", it only relieves the
> guarantees you normally get from the compiler. An `unsafe` implementation can
> Unsafe code does not mean it's literally "unsafe", it only relieves the
> guarantees you normally get from the compiler. An `unsafe` implementation can
> be perfectly safe to do, but you have no safety net.
@@ -605,7 +605,7 @@ extra care must be taken when implementing `Drop` for pinned types.
## Putting it all together
This is exactly what we'll do when we implement our own `Futures` stay tuned,
This is exactly what we'll do when we implement our own `Future`, so stay tuned,
we're soon finished.
[rfc2349]: https://github.com/rust-lang/rfcs/blob/master/text/2349-pin.md
@@ -637,7 +637,7 @@ pub fn main() {
let mut pinned1 = Box::pin(gen1);
let mut pinned2 = Box::pin(gen2);
// Uncomment these if you think it's safe to pin the values to the stack instead
// Uncomment these if you think it's safe to pin the values to the stack instead
// (it is in this case). Remember to comment out the two previous lines first.
//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };
//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };
@@ -645,7 +645,7 @@ pub fn main() {
if let GeneratorState::Yielded(n) = pinned1.as_mut().resume() {
println!("Gen1 got value {}", n);
}
if let GeneratorState::Yielded(n) = pinned2.as_mut().resume() {
println!("Gen2 got value {}", n);
};
@@ -660,8 +660,8 @@ pub fn main() {
}
enum GeneratorState<Y, R> {
Yielded(Y),
Complete(R),
Yielded(Y),
Complete(R),
}
trait Generator {
@@ -738,4 +738,4 @@ they did their unsafe implementation.
Hopefully, after this you'll have an idea of what happens when you use the
`yield` or `await` keywords inside an async function, and why we need `Pin` if
we want to be able to safely borrow across `yield/await` points.
we want to be able to safely borrow across `yield/await` points.
+227 -190
View File
@@ -1,6 +1,6 @@
# Implementing Futures - main example
We'll create our own `Futures` together with a fake reactor and a simple
We'll create our own Futures together with a fake reactor and a simple
executor which allows you to edit, run an play around with the code right here
in your browser.
@@ -23,9 +23,9 @@ Let's start off by getting all our imports right away so you can follow along
```rust, noplaypen, ignore
use std::{
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread::{self, JoinHandle}, time::{Duration, Instant}
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
};
```
@@ -51,8 +51,8 @@ a `Future` has resolved and should be polled again.
fn block_on<F: Future>(mut future: F) -> F::Output {
// the first thing we do is to construct a `Waker` which we'll pass on to
// the `reactor` so it can wake us up when an event is ready.
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
// the `reactor` so it can wake us up when an event is ready.
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
let waker = waker_into_waker(Arc::into_raw(mywaker));
// The context struct is just a wrapper for a `Waker` object. Maybe in the
@@ -69,10 +69,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
// an event occurs, or a thread has a "spurious wakeup" (an unexpected wakeup
// that can happen for no good reason).
let val = loop {
let val = loop {
match Future::poll(future, &mut cx) {
// when the Future is ready we're finished
Poll::Ready(val) => break val,
@@ -88,26 +87,26 @@ 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
here and focus only on some important aspects that might need further explanation.
Now that you've read so much about `Generators` 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
is a `yield` point. We could borrow data across `await` points and we meet the
exact same challenges as we do when borrowing across `yield` points.
> `Context` is just a wrapper around the `Waker`. At the time of writing this
book it's nothing more. In the future it might be possible that the `Context`
object will do more than just wrapping a `Future` so having this extra
object will do more than just wrapping a `Future` so having this extra
abstraction gives some flexibility.
As explained in the [chapter about generators](./3_generators_pin.md), we use
`Pin` and the guarantees that give us to allow `Futures` to have self
`Pin` and the guarantees that give us to allow `Future`s to have self
references.
## The `Future` implementation
Futures has a well defined interface, which means they can be used across the
entire ecosystem.
entire ecosystem.
We can chain these `Futures` so that once a **leaf-future** is
We can chain these `Future`s so that once a **leaf-future** is
ready we'll perform a set of operations until either the task is finished or we
reach yet another **leaf-future** which we'll wait for and yield control to the
scheduler.
@@ -131,9 +130,8 @@ struct MyWaker {
#[derive(Clone)]
pub struct Task {
id: usize,
reactor: Arc<Mutex<Reactor>>,
reactor: Arc<Mutex<Box<Reactor>>>,
data: u64,
is_registered: bool,
}
// These are function definitions we'll use for our waker. Remember the
@@ -173,48 +171,57 @@ fn waker_into_waker(s: *const MyWaker) -> Waker {
}
impl Task {
fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> Self {
Task {
id,
reactor,
data,
is_registered: false,
}
fn new(reactor: Arc<Mutex<Box<Reactor>>>, data: u64, id: usize) -> Self {
Task { id, reactor, data }
}
}
// This is our `Future` implementation
impl Future for Task {
// The output for our kind of `leaf future` is just an `usize`. For other
// futures this could be something more interesting like a byte array.
type Output = usize;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Poll is the what drives the state machine forward and it's the only
// method we'll need to call to drive futures to completion.
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// We need to get access the reactor in our `poll` method so we acquire
// a lock on that.
let mut r = self.reactor.lock().unwrap();
// we check with the `Reactor` if this future is in its "readylist"
// i.e. if it's `Ready`
// First we check if the task is marked as ready
if r.is_ready(self.id) {
// if it is, we return the data. In this case it's just the ID of
// the task since this is just a very simple example.
// If it's ready we set its state to `Finished`
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
Poll::Ready(self.id)
} else if self.is_registered {
// If it isn't finished we check the map we have stored in our Reactor
// over id's we have registered and see if it's there
} else if r.tasks.contains_key(&self.id) {
// If the future is registered alredy, we just return `Pending`
// This is important. The docs says that on multiple calls to poll,
// only the Waker from the Context passed to the most recent call
// should be scheduled to receive a wakeup. That's why we insert
// this waker into the map (which will return the old one which will
// get dropped) before we return `Pending`.
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
Poll::Pending
} else {
// If we get here, it must be the first time this `Future` is polled
// so we register a task with our `reactor`
// If it's not ready, and not in the map it's a new task so we
// register that with the Reactor and return `Pending`
r.register(self.data, cx.waker().clone(), self.id);
// oh, we have to drop the lock on our `Mutex` here because we can't
// have a shared and exclusive borrow at the same time
drop(r);
self.is_registered = true;
Poll::Pending
}
// Note that we're holding a lock on the `Mutex` which protects the
// Reactor all the way until the end of this scope. This means that
// even if our task were to complete immidiately, it will not be
// able to call `wake` while we're in our `Poll` method.
// Since we can make this guarantee, it's now the Executors job to
// handle this possible race condition where `Wake` is called after
// `poll` but before our thread goes to sleep.
}
}
```
@@ -230,9 +237,9 @@ is just increasing the refcount in this case.
Dropping a `Waker` is as easy as decreasing the refcount. Now, in special
cases we could choose to not use an `Arc`. So this low-level method is there
to allow such cases.
to allow such cases.
Indeed, if we only used `Arc` there is no reason for us to go through all the
Indeed, if we only used `Arc` there is no reason for us to go through all the
trouble of creating our own `vtable` and a `RawWaker`. We could just implement
a normal trait.
@@ -245,13 +252,13 @@ The reactor will often be a global resource which let's us register interests
without passing around a reference.
> ### 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`
> and call park/unpark on it.
>
>
> 1. A future could call `unpark` on the executor thread from a different thread
> 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
> 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.
@@ -277,13 +284,13 @@ Since concurrency mostly makes sense when interacting with the outside world (or
at least some peripheral), we need something to actually abstract over this
interaction in an asynchronous way.
This is the `Reactors` job. Most often you'll see reactors in Rust use a library
This is the Reactors job. Most often you'll see reactors in Rust use a library
called [Mio][mio], which provides non blocking APIs and event notification for
several platforms.
The reactor will typically give you something like a `TcpStream` (or any other
resource) which you'll use to create an I/O request. What you get in return is a
`Future`.
`Future`.
>If our reactor did some real I/O work our `Task` in would instead be represent
>a non-blocking `TcpStream` which registers interest with the global `Reactor`.
@@ -303,6 +310,15 @@ for the sake of this example.
**Our Reactor will look like this:**
```rust, noplaypen, ignore
// This is a "fake" reactor. It does no real I/O, but that also makes our
// code possible to run in the book and in the playground
// The different states a task can have in this Reactor
enum TaskState {
Ready,
NotReady(Waker),
Finished,
}
// This is a "fake" reactor. It does no real I/O, but that also makes our
// code possible to run in the book and in the playground
struct Reactor {
@@ -312,106 +328,118 @@ struct Reactor {
dispatcher: Sender<Event>,
handle: Option<JoinHandle<()>>,
// This is a list of tasks that are ready, which means they should be polled
// for data.
readylist: Arc<Mutex<Vec<usize>>>,
// This is a list of tasks
tasks: HashMap<usize, TaskState>,
}
// We just have two kind of events. An event called `Timeout`
// and a `Close` event to close down our reactor.
// This represents the Events we can send to our reactor thread. In this
// example it's only a Timeout or a Close event.
#[derive(Debug)]
enum Event {
Close,
Timeout(Waker, u64, usize),
Timeout(u64, usize),
}
impl Reactor {
fn new() -> Self {
// The way we register new events with our reactor is using a regular
// channel
// We choose to return an atomic reference counted, mutex protected, heap
// allocated `Reactor`. Just to make it easy to explain... No, the reason
// we do this is:
//
// 1. We know that only thread-safe reactors will be created.
// 2. By heap allocating it we can obtain a reference to a stable address
// that's not dependent on the stack frame of the function that called `new`
fn new() -> Arc<Mutex<Box<Self>>> {
let (tx, rx) = channel::<Event>();
let readylist = Arc::new(Mutex::new(vec![]));
let rl_clone = readylist.clone();
let reactor = Arc::new(Mutex::new(Box::new(Reactor {
dispatcher: tx,
handle: None,
tasks: HashMap::new(),
})));
// Notice that we'll need to use `weak` reference here. If we don't,
// our `Reactor` will not get `dropped` when our main thread is finished
// since we're holding internal references to it.
// This `Vec` will hold handles to all the threads we spawn so we can
// join them later on and finish our programm in a good manner
let mut handles = vec![];
// Since we're collecting all `JoinHandles` from the threads we spawn
// and make sure to join them we know that `Reactor` will be alive
// longer than any reference held by the threads we spawn here.
let reactor_clone = Arc::downgrade(&reactor);
// This will be the "Reactor thread"
// This will be our Reactor-thread. The Reactor-thread will in our case
// just spawn new threads which will serve as timers for us.
let handle = thread::spawn(move || {
let mut handles = vec![];
// This simulates some I/O resource
for event in rx {
let rl_clone = rl_clone.clone();
println!("REACTOR: {:?}", event);
let reactor = reactor_clone.clone();
match event {
// If we get a close event we break out of the loop we're in
Event::Close => break,
Event::Timeout(waker, duration, id) => {
Event::Timeout(duration, id) => {
// When we get an event we simply spawn a new thread
// which will simulate some I/O resource...
// We spawn a new thread that will serve as a timer
// and will call `wake` on the correct `Waker` once
// it's done.
let event_handle = thread::spawn(move || {
//... by sleeping for the number of seconds
// we provided when creating the `Task`.
thread::sleep(Duration::from_secs(duration));
// When it's done sleeping we put the ID of this task
// on the "readylist"
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
// Then we call `wake` which will wake up our
// executor and start polling the futures
waker.wake();
let reactor = reactor.upgrade().unwrap();
reactor.lock().map(|mut r| r.wake(id)).unwrap();
});
handles.push(event_handle);
}
}
}
// When we exit the Reactor we first join all the handles on
// the child threads we've spawned so we catch any panics and
// release any resources.
for handle in handles {
handle.join().unwrap();
}
// This is important for us since we need to know that these
// threads don't live longer than our Reactor-thread. Our
// Reactor-thread will be joined when `Reactor` gets dropped.
handles.into_iter().for_each(|handle| handle.join().unwrap());
});
reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
reactor
}
Reactor {
readylist,
dispatcher: tx,
handle: Some(handle),
// The wake function will call wake on the waker for the task with the
// corresponding id.
fn wake(&mut self, id: usize) {
self.tasks.get_mut(&id).map(|state| {
// No matter what state the task was in we can safely set it
// to ready at this point. This lets us get ownership over the
// the data that was there before we replaced it.
match mem::replace(state, TaskState::Ready) {
TaskState::NotReady(waker) => waker.wake(),
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
_ => unreachable!()
}
}).unwrap();
}
// Register a new task with the reactor. In this particular example
// we panic if a task with the same id get's registered twice
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
panic!("Tried to insert a task with id: '{}', twice!", id);
}
self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
}
fn register(&mut self, duration: u64, waker: Waker, data: usize) {
// registering an event is as simple as sending an `Event` through
// the channel.
self.dispatcher
.send(Event::Timeout(waker, duration, data))
.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 need a way to check if any event's are ready. This will simply
// look through the "readylist" for an event macthing the ID we want to
// check for.
fn is_ready(&self, id_to_check: usize) -> bool {
self.readylist
.lock()
.map(|rl| rl.iter().any(|id| *id == id_to_check))
.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 {
TaskState::Ready => true,
_ => false,
}).unwrap_or(false)
}
}
// When our `Reactor` is dropped we join the reactor thread with the thread
// owning our `Reactor` so we catch any panics and release all resources.
// It's not needed for this to work, but it really is a best practice to join
// all threads you spawn.
impl Drop for Reactor {
fn drop(&mut self) {
self.handle.take().map(|h| h.join().unwrap()).unwrap();
@@ -430,21 +458,17 @@ which you can edit and change the way you like.
```rust, edition2018
# use std::{
# future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
# task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
# thread::{self, JoinHandle}, time::{Duration, Instant}
# future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
# task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, mem,
# thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
# };
#
#
fn main() {
// This is just to make it easier for us to see when our Future was resolved
let start = Instant::now();
// Many runtimes create a glocal `reactor` we pass it as an argument
let reactor = Reactor::new();
// Since we'll share this between threads we wrap it in a
// atmically-refcounted- mutex.
let reactor = Arc::new(Mutex::new(reactor));
// We create two tasks:
// - first parameter is the `reactor`
@@ -482,157 +506,171 @@ fn main() {
// ends nicely.
reactor.lock().map(|mut r| r.close()).unwrap();
}
# // ============================= EXECUTOR ====================================
# fn block_on<F: Future>(mut future: F) -> F::Output {
# let mywaker = Arc::new(MyWaker{ thread: thread::current() });
# let mywaker = Arc::new(MyWaker {
# 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.
# let mut future = unsafe { Pin::new_unchecked(&mut future) };
# let val = loop {
# let pinned = unsafe { Pin::new_unchecked(&mut future) };
# match Future::poll(pinned, &mut cx) {
# match Future::poll(future.as_mut(), &mut cx) {
# Poll::Ready(val) => break val,
# Poll::Pending => thread::park(),
# };
# };
# val
# }
#
#
# // ====================== FUTURE IMPLEMENTATION ==============================
# #[derive(Clone)]
# struct MyWaker {
# thread: thread::Thread,
# }
#
#
# #[derive(Clone)]
# pub struct Task {
# id: usize,
# reactor: Arc<Mutex<Reactor>>,
# reactor: Arc<Mutex<Box<Reactor>>>,
# data: u64,
# is_registered: bool,
# }
#
#
# fn mywaker_wake(s: &MyWaker) {
# let waker_ptr: *const MyWaker = s;
# let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
# let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
# waker_arc.thread.unpark();
# }
#
#
# fn mywaker_clone(s: &MyWaker) -> RawWaker {
# let arc = unsafe { Arc::from_raw(s).clone() };
# let arc = unsafe { Arc::from_raw(s) };
# std::mem::forget(arc.clone()); // increase ref count
# RawWaker::new(Arc::into_raw(arc) as *const (), &VTABLE)
# }
#
#
# const VTABLE: RawWakerVTable = unsafe {
# RawWakerVTable::new(
# |s| mywaker_clone(&*(s as *const MyWaker)), // clone
# |s| mywaker_wake(&*(s as *const MyWaker)), // wake
# |s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
# |s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
# |s| mywaker_clone(&*(s as *const MyWaker)), // clone
# |s| mywaker_wake(&*(s as *const MyWaker)), // wake
# |s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
# |s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
# )
# };
#
#
# fn waker_into_waker(s: *const MyWaker) -> Waker {
# let raw_waker = RawWaker::new(s as *const (), &VTABLE);
# unsafe { Waker::from_raw(raw_waker) }
# }
#
#
# impl Task {
# fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> Self {
# Task {
# id,
# reactor,
# data,
# is_registered: false,
# }
# fn new(reactor: Arc<Mutex<Box<Reactor>>>, data: u64, id: usize) -> Self {
# Task { id, reactor, data }
# }
# }
#
#
# impl Future for Task {
# type Output = usize;
# fn poll(mut 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();
# if r.is_ready(self.id) {
# println!("POLL: TASK {} IS READY", self.id);
# *r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
# Poll::Ready(self.id)
# } else if self.is_registered {
# } 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()));
# Poll::Pending
# } else {
# println!("POLL: REGISTERED TASK: {}, WAKER: {:?}", self.id, cx.waker());
# r.register(self.data, cx.waker().clone(), self.id);
# drop(r);
# self.is_registered = true;
# Poll::Pending
# }
# }
# }
#
#
# // =============================== REACTOR ===================================
# enum TaskState {
# Ready,
# NotReady(Waker),
# Finished,
# }
# struct Reactor {
# dispatcher: Sender<Event>,
# handle: Option<JoinHandle<()>>,
# readylist: Arc<Mutex<Vec<usize>>>,
# tasks: HashMap<usize, TaskState>,
# }
#
# #[derive(Debug)]
# enum Event {
# Close,
# Timeout(Waker, u64, usize),
# Timeout(u64, usize),
# }
#
#
# impl Reactor {
# fn new() -> Self {
# fn new() -> Arc<Mutex<Box<Self>>> {
# let (tx, rx) = channel::<Event>();
# let readylist = Arc::new(Mutex::new(vec![]));
# let rl_clone = readylist.clone();
# let mut handles = vec![];
# let reactor = Arc::new(Mutex::new(Box::new(Reactor {
# dispatcher: tx,
# handle: None,
# tasks: HashMap::new(),
# })));
#
# let reactor_clone = Arc::downgrade(&reactor);
# let handle = thread::spawn(move || {
# let mut handles = vec![];
# // This simulates some I/O resource
# for event in rx {
# println!("REACTOR: {:?}", event);
# let rl_clone = rl_clone.clone();
# let reactor = reactor_clone.clone();
# match event {
# Event::Close => break,
# Event::Timeout(waker, duration, id) => {
# Event::Timeout(duration, id) => {
# let event_handle = thread::spawn(move || {
# thread::sleep(Duration::from_secs(duration));
# rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
# waker.wake();
# let reactor = reactor.upgrade().unwrap();
# reactor.lock().map(|mut r| r.wake(id)).unwrap();
# });
#
# handles.push(event_handle);
# }
# }
# }
#
# for handle in handles {
# handle.join().unwrap();
# }
# handles.into_iter().for_each(|handle| handle.join().unwrap());
# });
# reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
# reactor
# }
#
# Reactor {
# readylist,
# dispatcher: tx,
# handle: Some(handle),
# fn wake(&mut self, id: usize) {
# self.tasks.get_mut(&id).map(|state| {
# match mem::replace(state, TaskState::Ready) {
# TaskState::NotReady(waker) => waker.wake(),
# TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
# _ => unreachable!()
# }
# }).unwrap();
# }
#
# fn register(&mut self, duration: u64, waker: Waker, id: usize) {
# if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
# panic!("Tried to insert a task with id: '{}', twice!", id);
# }
# self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
# }
#
# fn register(&mut self, duration: u64, waker: Waker, data: usize) {
# self.dispatcher
# .send(Event::Timeout(waker, duration, data))
# .unwrap();
# }
#
#
# fn close(&mut self) {
# self.dispatcher.send(Event::Close).unwrap();
# }
#
# fn is_ready(&self, id_to_check: usize) -> bool {
# self.readylist
# .lock()
# .map(|rl| rl.iter().any(|id| *id == id_to_check))
# .unwrap()
# fn is_ready(&self, id: usize) -> bool {
# self.tasks.get(&id).map(|state| match state {
# TaskState::Ready => true,
# _ => false,
# }).unwrap_or(false)
# }
# }
#
#
# impl Drop for Reactor {
# fn drop(&mut self) {
# self.handle.take().map(|h| h.join().unwrap()).unwrap();
@@ -640,11 +678,10 @@ fn main() {
# }
```
I added a debug printout of the events the reactor registered interest for so we can observe
two things:
I added a some debug printouts so we can observe a couple of things:
1. How the `Waker` object looks just like the _trait object_ we talked about in an earlier chapter
2. In what order the events register interest with the reactor
2. The program flow from start to finish
The last point is relevant when we move on the the last paragraph.
@@ -654,7 +691,7 @@ The `async` keyword can be used on functions as in `async fn(...)` or on a
block as in `async { ... }`. Both will turn your function, or block, into a
`Future`.
These `Futures` are rather simple. Imagine our generator from a few chapters
These Futures are rather simple. Imagine our generator from a few chapters
back. Every `await` point is like a `yield` point.
Instead of `yielding` a value we pass in, we yield the result of calling `poll` on
@@ -675,7 +712,7 @@ Future got 1 at time: 1.00.
Future got 2 at time: 3.00.
```
If these `Futures` were executed asynchronously we would expect to see:
If these Futures were executed asynchronously we would expect to see:
```ignore
Future got 1 at time: 1.00.
@@ -697,7 +734,7 @@ how they implement different ways of running Futures to completion.
[If I were you I would read this next, and try to implement it for our example.](./conclusion.md#building-a-better-exectuor).
That's actually it for now. There as probably much more to learn, this is enough
for today.
for today.
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.
+67 -58
View File
@@ -5,16 +5,9 @@ Here is the whole example. You can edit it right here in your browser and
run it yourself. Have fun!
```rust,editable,edition2018
use std::{
future::Future, pin::Pin, sync::{mpsc::{channel, Sender}, Arc, Mutex},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread::{self, JoinHandle}, time::{Duration, Instant}
};
fn main() {
let start = Instant::now();
let reactor = Reactor::new();
let reactor = Arc::new(Mutex::new(reactor));
let future1 = Task::new(reactor.clone(), 1, 1);
let future2 = Task::new(reactor.clone(), 2, 2);
@@ -38,10 +31,17 @@ fn main() {
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,
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
};
// ============================= EXECUTOR ====================================
fn block_on<F: Future>(mut future: F) -> F::Output {
let mywaker = Arc::new(MyWaker{ thread: thread::current() });
let mywaker = Arc::new(MyWaker {
thread: thread::current(),
});
let waker = waker_into_waker(Arc::into_raw(mywaker));
let mut cx = Context::from_waker(&waker);
@@ -65,14 +65,13 @@ struct MyWaker {
#[derive(Clone)]
pub struct Task {
id: usize,
reactor: Arc<Mutex<Reactor>>,
reactor: Arc<Mutex<Box<Reactor>>>,
data: u64,
is_registered: bool,
}
fn mywaker_wake(s: &MyWaker) {
let waker_ptr: *const MyWaker = s;
let waker_arc = unsafe {Arc::from_raw(waker_ptr)};
let waker_arc = unsafe { Arc::from_raw(waker_ptr) };
waker_arc.thread.unpark();
}
@@ -84,10 +83,10 @@ fn mywaker_clone(s: &MyWaker) -> RawWaker {
const VTABLE: RawWakerVTable = unsafe {
RawWakerVTable::new(
|s| mywaker_clone(&*(s as *const MyWaker)), // clone
|s| mywaker_wake(&*(s as *const MyWaker)), // wake
|s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
|s| mywaker_clone(&*(s as *const MyWaker)), // clone
|s| mywaker_wake(&*(s as *const MyWaker)), // wake
|s| mywaker_wake(*(s as *const &MyWaker)), // wake by ref
|s| drop(Arc::from_raw(s as *const MyWaker)), // decrease refcount
)
};
@@ -97,97 +96,107 @@ fn waker_into_waker(s: *const MyWaker) -> Waker {
}
impl Task {
fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> Self {
Task {
id,
reactor,
data,
is_registered: false,
}
fn new(reactor: Arc<Mutex<Box<Reactor>>>, data: u64, id: usize) -> Self {
Task { id, reactor, data }
}
}
impl Future for Task {
type Output = usize;
fn poll(mut 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();
if r.is_ready(self.id) {
println!("POLL: TASK {} IS READY", self.id);
*r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
Poll::Ready(self.id)
} else if self.is_registered {
} 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()));
Poll::Pending
} else {
println!("POLL: REGISTERED TASK: {}, WAKER: {:?}", self.id, cx.waker());
r.register(self.data, cx.waker().clone(), self.id);
drop(r);
self.is_registered = true;
Poll::Pending
}
}
}
// =============================== REACTOR ===================================
enum TaskState {
Ready,
NotReady(Waker),
Finished,
}
struct Reactor {
dispatcher: Sender<Event>,
handle: Option<JoinHandle<()>>,
readylist: Arc<Mutex<Vec<usize>>>,
tasks: HashMap<usize, TaskState>,
}
#[derive(Debug)]
enum Event {
Close,
Timeout(Waker, u64, usize),
Timeout(u64, usize),
}
impl Reactor {
fn new() -> Self {
fn new() -> Arc<Mutex<Box<Self>>> {
let (tx, rx) = channel::<Event>();
let readylist = Arc::new(Mutex::new(vec![]));
let rl_clone = readylist.clone();
let mut handles = vec![];
let reactor = Arc::new(Mutex::new(Box::new(Reactor {
dispatcher: tx,
handle: None,
tasks: HashMap::new(),
})));
let reactor_clone = Arc::downgrade(&reactor);
let handle = thread::spawn(move || {
// This simulates some I/O resource
let mut handles = vec![];
for event in rx {
println!("REACTOR: {:?}", event);
let rl_clone = rl_clone.clone();
let reactor = reactor_clone.clone();
match event {
Event::Close => break,
Event::Timeout(waker, duration, id) => {
Event::Timeout(duration, id) => {
let event_handle = thread::spawn(move || {
thread::sleep(Duration::from_secs(duration));
rl_clone.lock().map(|mut rl| rl.push(id)).unwrap();
waker.wake();
let reactor = reactor.upgrade().unwrap();
reactor.lock().map(|mut r| r.wake(id)).unwrap();
});
handles.push(event_handle);
}
}
}
for handle in handles {
handle.join().unwrap();
}
handles.into_iter().for_each(|handle| handle.join().unwrap());
});
Reactor {
readylist,
dispatcher: tx,
handle: Some(handle),
}
reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
reactor
}
fn register(&mut self, duration: u64, waker: Waker, data: usize) {
self.dispatcher
.send(Event::Timeout(waker, duration, data))
.unwrap();
fn wake(&mut self, id: usize) {
self.tasks.get_mut(&id).map(|state| {
match mem::replace(state, TaskState::Ready) {
TaskState::NotReady(waker) => waker.wake(),
TaskState::Finished => panic!("Called 'wake' twice on task: {}", id),
_ => unreachable!()
}
}).unwrap();
}
fn register(&mut self, duration: u64, waker: Waker, id: usize) {
if self.tasks.insert(id, TaskState::NotReady(waker)).is_some() {
panic!("Tried to insert a task with id: '{}', twice!", id);
}
self.dispatcher.send(Event::Timeout(duration, id)).unwrap();
}
fn close(&mut self) {
self.dispatcher.send(Event::Close).unwrap();
}
fn is_ready(&self, id_to_check: usize) -> bool {
self.readylist
.lock()
.map(|rl| rl.iter().any(|id| *id == id_to_check))
.unwrap()
fn is_ready(&self, id: usize) -> bool {
self.tasks.get(&id).map(|state| match state {
TaskState::Ready => true,
_ => false,
}).unwrap_or(false)
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
# Futures Explained in 200 Lines of Rust
This book aims to explain `Futures` in Rust using an example driven approach,
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
in programming.
@@ -31,7 +31,7 @@ I've limited myself to a 200 line main example (hence the title) to limit the
scope and introduce an example that can easily be explored further.
However, there is a lot to digest and it's not what I would call easy, but we'll
take everything step by step so get a cup of tea and relax.
take everything step by step so get a cup of tea and relax.
I hope you enjoy the ride.
@@ -57,8 +57,8 @@ in Rust. If you like it, you might want to check out the others as well:
## Credits and thanks
I'd like to take this chance to thank the people behind `mio`, `tokio`,
`async_std`, `Futures`, `libc`, `crossbeam` which underpins so much of the
async ecosystem and rarely gets enough praise in my eyes.
`async_std`, `futures`, `libc`, `crossbeam` which underpins so much of the
async ecosystem and and rarely gets enough praise in my eyes.
A special thanks to [jonhoo](https://twitter.com/jonhoo) who was kind enough to
give me some valuable feedback on a very early draft of this book. He has not
@@ -66,4 +66,4 @@ read the finished product, but a big thanks is definitely due.
[mdbook]: https://github.com/rust-lang/mdBook
[book_repo]: https://github.com/cfsamson/books-futures-explained
[example_repo]: https://github.com/cfsamson/examples-futures
[example_repo]: https://github.com/cfsamson/examples-futures