several improvements, see #2 for more details
This commit is contained in:
@@ -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.
|
||||
|
||||
+203
-218
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html" class="active"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html" class="active"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -280,199 +277,199 @@ It's not in any way meant to showcase "best practice". Just so we're o
|
||||
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>
|
||||
<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">
|
||||
</span><span class="boring">const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
|
||||
</span><span class="boring">const MAX_THREADS: usize = 4;
|
||||
</span><span class="boring">static mut RUNTIME: usize = 0;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">pub struct Runtime {
|
||||
</span><span class="boring"> threads: Vec<Thread>,
|
||||
</span><span class="boring"> current: usize,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[derive(PartialEq, Eq, Debug)]
|
||||
</span><span class="boring">enum State {
|
||||
</span><span class="boring"> Available,
|
||||
</span><span class="boring"> Running,
|
||||
</span><span class="boring"> Ready,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">struct Thread {
|
||||
</span><span class="boring"> id: usize,
|
||||
</span><span class="boring"> stack: Vec<u8>,
|
||||
</span><span class="boring"> ctx: ThreadContext,
|
||||
</span><span class="boring"> state: State,
|
||||
</span><span class="boring"> task: Option<Box<dyn Fn()>>,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[derive(Debug, Default)]
|
||||
</span><span class="boring">#[repr(C)]
|
||||
</span><span class="boring">struct ThreadContext {
|
||||
</span><span class="boring"> rsp: u64,
|
||||
</span><span class="boring"> r15: u64,
|
||||
</span><span class="boring"> r14: u64,
|
||||
</span><span class="boring"> r13: u64,
|
||||
</span><span class="boring"> r12: u64,
|
||||
</span><span class="boring"> rbx: u64,
|
||||
</span><span class="boring"> rbp: u64,
|
||||
</span><span class="boring"> thread_ptr: u64,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Thread {
|
||||
</span><span class="boring"> fn new(id: usize) -> Self {
|
||||
</span><span class="boring"> Thread {
|
||||
</span><span class="boring"> id,
|
||||
</span><span class="boring"> stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
</span><span class="boring"> ctx: ThreadContext::default(),
|
||||
</span><span class="boring"> state: State::Available,
|
||||
</span><span class="boring"> task: None,
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Runtime {
|
||||
</span><span class="boring"> pub fn new() -> Self {
|
||||
</span><span class="boring"> let base_thread = Thread {
|
||||
</span><span class="boring"> id: 0,
|
||||
</span><span class="boring"> stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
</span><span class="boring"> ctx: ThreadContext::default(),
|
||||
</span><span class="boring"> state: State::Running,
|
||||
</span><span class="boring"> task: None,
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let mut threads = vec![base_thread];
|
||||
</span><span class="boring"> threads[0].ctx.thread_ptr = &threads[0] as *const Thread as u64;
|
||||
</span><span class="boring"> let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
|
||||
</span><span class="boring"> threads.append(&mut available_threads);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> Runtime {
|
||||
</span><span class="boring"> threads,
|
||||
</span><span class="boring"> current: 0,
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn init(&self) {
|
||||
</span><span class="boring"> unsafe {
|
||||
</span><span class="boring"> let r_ptr: *const Runtime = self;
|
||||
</span><span class="boring"> RUNTIME = r_ptr as usize;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn run(&mut self) -> ! {
|
||||
</span><span class="boring"> while self.t_yield() {}
|
||||
</span><span class="boring"> std::process::exit(0);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn t_return(&mut self) {
|
||||
</span><span class="boring"> if self.current != 0 {
|
||||
</span><span class="boring"> self.threads[self.current].state = State::Available;
|
||||
</span><span class="boring"> self.t_yield();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn t_yield(&mut self) -> bool {
|
||||
</span><span class="boring"> let mut pos = self.current;
|
||||
</span><span class="boring"> while self.threads[pos].state != State::Ready {
|
||||
</span><span class="boring"> pos += 1;
|
||||
</span><span class="boring"> if pos == self.threads.len() {
|
||||
</span><span class="boring"> pos = 0;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> if pos == self.current {
|
||||
</span><span class="boring"> return false;
|
||||
</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"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> self.threads[pos].state = State::Running;
|
||||
</span><span class="boring"> let old_pos = self.current;
|
||||
</span><span class="boring"> self.current = pos;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> unsafe {
|
||||
</span><span class="boring"> switch(&mut self.threads[old_pos].ctx, &self.threads[pos].ctx);
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> true
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> pub fn spawn<F: Fn() + 'static>(f: F){
|
||||
</span><span class="boring"> unsafe {
|
||||
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
|
||||
</span><span class="boring"> let available = (*rt_ptr)
|
||||
</span><span class="boring"> .threads
|
||||
</span><span class="boring"> .iter_mut()
|
||||
</span><span class="boring"> .find(|t| t.state == State::Available)
|
||||
</span><span class="boring"> .expect("no available thread.");
|
||||
</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));
|
||||
</span><span class="boring"> available.ctx.thread_ptr = available as *const Thread as u64;
|
||||
</span><span class="boring"> ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64);
|
||||
</span><span class="boring"> ptr::write(s_ptr.offset((size - 16) as isize) as *mut u64, call as u64);
|
||||
</span><span class="boring"> available.ctx.rsp = s_ptr.offset((size - 16) as isize) as u64;
|
||||
</span><span class="boring"> available.state = State::Ready;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn call(thread: u64) {
|
||||
</span><span class="boring"> let thread = unsafe { &*(thread as *const Thread) };
|
||||
</span><span class="boring"> if let Some(f) = &thread.task {
|
||||
</span><span class="boring"> f();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[naked]
|
||||
</span><span class="boring">fn guard() {
|
||||
</span><span class="boring"> unsafe {
|
||||
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
|
||||
</span><span class="boring"> let rt = &mut *rt_ptr;
|
||||
</span><span class="boring"> println!("THREAD {} FINISHED.", rt.threads[rt.current].id);
|
||||
</span><span class="boring"> rt.t_return();
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">pub fn yield_thread() {
|
||||
</span><span class="boring"> unsafe {
|
||||
</span><span class="boring"> let rt_ptr = RUNTIME as *mut Runtime;
|
||||
</span><span class="boring"> (*rt_ptr).t_yield();
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[naked]
|
||||
</span><span class="boring">#[inline(never)]
|
||||
</span><span class="boring">unsafe fn switch(old: *mut ThreadContext, new: *const ThreadContext) {
|
||||
</span><span class="boring"> asm!("
|
||||
</span><span class="boring"> mov %rsp, 0x00($0)
|
||||
</span><span class="boring"> mov %r15, 0x08($0)
|
||||
</span><span class="boring"> mov %r14, 0x10($0)
|
||||
</span><span class="boring"> mov %r13, 0x18($0)
|
||||
</span><span class="boring"> mov %r12, 0x20($0)
|
||||
</span><span class="boring"> mov %rbx, 0x28($0)
|
||||
</span><span class="boring"> mov %rbp, 0x30($0)
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> mov 0x00($1), %rsp
|
||||
</span><span class="boring"> mov 0x08($1), %r15
|
||||
</span><span class="boring"> mov 0x10($1), %r14
|
||||
</span><span class="boring"> mov 0x18($1), %r13
|
||||
</span><span class="boring"> mov 0x20($1), %r12
|
||||
</span><span class="boring"> mov 0x28($1), %rbx
|
||||
</span><span class="boring"> mov 0x30($1), %rbp
|
||||
</span><span class="boring"> mov 0x38($1), %rdi
|
||||
</span><span class="boring"> ret
|
||||
</span><span class="boring"> "
|
||||
</span><span class="boring"> :
|
||||
</span><span class="boring"> : "r"(old), "r"(new)
|
||||
</span><span class="boring"> :
|
||||
</span><span class="boring"> : "alignstack"
|
||||
</span><span class="boring"> );
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">#[cfg(not(windows))]
|
||||
</span>fn main() {
|
||||
<pre><pre class="playpen"><code class="language-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>,
|
||||
# ctx: ThreadContext,
|
||||
# state: State,
|
||||
# task: Option<Box<dyn Fn()>>,
|
||||
# }
|
||||
#
|
||||
# #[derive(Debug, Default)]
|
||||
# #[repr(C)]
|
||||
# struct ThreadContext {
|
||||
# rsp: u64,
|
||||
# r15: u64,
|
||||
# r14: u64,
|
||||
# r13: u64,
|
||||
# r12: u64,
|
||||
# rbx: u64,
|
||||
# rbp: u64,
|
||||
# thread_ptr: u64,
|
||||
# }
|
||||
#
|
||||
# impl Thread {
|
||||
# fn new(id: usize) -> Self {
|
||||
# Thread {
|
||||
# id,
|
||||
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
# ctx: ThreadContext::default(),
|
||||
# state: State::Available,
|
||||
# task: None,
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# impl Runtime {
|
||||
# pub fn new() -> Self {
|
||||
# let base_thread = Thread {
|
||||
# id: 0,
|
||||
# stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
# ctx: ThreadContext::default(),
|
||||
# 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 {
|
||||
# pos += 1;
|
||||
# if pos == self.threads.len() {
|
||||
# pos = 0;
|
||||
# }
|
||||
# if pos == self.current {
|
||||
# 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;
|
||||
# let available = (*rt_ptr)
|
||||
# .threads
|
||||
# .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));
|
||||
# available.ctx.thread_ptr = available as *const Thread as u64;
|
||||
# ptr::write(s_ptr.offset((size - 8) as isize) as *mut u64, guard as u64);
|
||||
# ptr::write(s_ptr.offset((size - 16) as isize) as *mut u64, call as u64);
|
||||
# available.ctx.rsp = s_ptr.offset((size - 16) as isize) as u64;
|
||||
# available.state = State::Ready;
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# fn call(thread: u64) {
|
||||
# let thread = unsafe { &*(thread as *const Thread) };
|
||||
# if let Some(f) = &thread.task {
|
||||
# f();
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# #[naked]
|
||||
# fn guard() {
|
||||
# unsafe {
|
||||
# let rt_ptr = RUNTIME as *mut Runtime;
|
||||
# let rt = &mut *rt_ptr;
|
||||
# println!("THREAD {} FINISHED.", rt.threads[rt.current].id);
|
||||
# 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) {
|
||||
# asm!("
|
||||
# mov %rsp, 0x00($0)
|
||||
# mov %r15, 0x08($0)
|
||||
# mov %r14, 0x10($0)
|
||||
# mov %r13, 0x18($0)
|
||||
# mov %r12, 0x20($0)
|
||||
# mov %rbx, 0x28($0)
|
||||
# mov %rbp, 0x30($0)
|
||||
#
|
||||
# mov 0x00($1), %rsp
|
||||
# mov 0x08($1), %r15
|
||||
# mov 0x10($1), %r14
|
||||
# mov 0x18($1), %r13
|
||||
# mov 0x20($1), %r12
|
||||
# mov 0x28($1), %rbx
|
||||
# mov 0x30($1), %rbp
|
||||
# mov 0x38($1), %rdi
|
||||
# ret
|
||||
# "
|
||||
# :
|
||||
# : "r"(old), "r"(new)
|
||||
# :
|
||||
# : "alignstack"
|
||||
# );
|
||||
# }
|
||||
# #[cfg(not(windows))]
|
||||
fn main() {
|
||||
let mut runtime = Runtime::new();
|
||||
runtime.init();
|
||||
Runtime::spawn(|| {
|
||||
@@ -488,9 +485,9 @@ the same page.</p>
|
||||
});
|
||||
runtime.run();
|
||||
}
|
||||
<span class="boring">#[cfg(windows)]
|
||||
</span><span class="boring">fn main() { }
|
||||
</span></code></pre></pre>
|
||||
# #[cfg(windows)]
|
||||
# fn main() { }
|
||||
</code></pre></pre>
|
||||
<p>Still hanging in there? Good. Don't get frustrated if the code above is
|
||||
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>
|
||||
@@ -738,18 +735,6 @@ need to be polled once before they do any work.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html" class="active"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html" class="active"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -197,7 +194,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
|
||||
@@ -242,7 +239,7 @@ 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>
|
||||
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 <code>Futures</code> as of writing this is:</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/async-rs/async-std">async-std</a></li>
|
||||
@@ -403,18 +400,6 @@ it needs to be, so go on and read these chapters if you feel a bit unsure. </p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html" class="active"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html" class="active"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -194,8 +191,8 @@ article written by Adam Schwalm called <a href="https://alschwalm.com/blog/stati
|
||||
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>
|
||||
<p>Run the following code <em>(You'll have to press "play" 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 { }
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::mem::size_of;
|
||||
trait SomeTrait { }
|
||||
|
||||
fn main() {
|
||||
println!("======== The size of different pointers in Rust: ========");
|
||||
@@ -380,18 +377,6 @@ use purely global functions and state, or any other way you wish.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+147
-164
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html" class="active"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html" class="active"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -351,7 +348,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 "manually" 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>
|
||||
@@ -363,19 +360,19 @@ trait for our generators which would let us do this:</p>
|
||||
Just keep this in the back of your head as we move forward.</p>
|
||||
<p>Now what does our rewritten state machine look like with this example?</p>
|
||||
<pre><pre class="playpen"><code class="language-rust compile_fail">
|
||||
<span class="boring">#![allow(unused_variables)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring">enum GeneratorState<Y, 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 {
|
||||
</span><span class="boring"> type Yield;
|
||||
</span><span class="boring"> type Return;
|
||||
</span><span class="boring"> fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
</span><span class="boring">}
|
||||
</span>
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
|
||||
enum GeneratorA {
|
||||
Enter,
|
||||
Yield1 {
|
||||
@@ -385,12 +382,12 @@ enum GeneratorA {
|
||||
Exit,
|
||||
}
|
||||
|
||||
<span class="boring">impl GeneratorA {
|
||||
</span><span class="boring"> fn start() -> Self {
|
||||
</span><span class="boring"> GeneratorA::Enter
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span>
|
||||
# impl GeneratorA {
|
||||
# fn start() -> Self {
|
||||
# GeneratorA::Enter
|
||||
# }
|
||||
# }
|
||||
|
||||
impl Generator for GeneratorA {
|
||||
type Yield = usize;
|
||||
type Return = ();
|
||||
@@ -415,8 +412,7 @@ impl Generator for GeneratorA {
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
#}</code></pre></pre>
|
||||
<p>If you try to compile this you'll get an error (just try it yourself by pressing play).</p>
|
||||
<p>What is the lifetime of <code>&String</code>. It's not the same as the lifetime of <code>Self</code>. It's not <code>static</code>.
|
||||
Turns out that it's not possible for us in Rusts syntax to describe this lifetime, which means, that
|
||||
@@ -427,9 +423,9 @@ see we end up in a <em>self referential struct</em>. A struct which holds refere
|
||||
into itself.</p>
|
||||
<p>As you'll notice, this compiles just fine!</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">
|
||||
<span class="boring">#![allow(unused_variables)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>enum GeneratorState<Y, R> {
|
||||
# #![allow(unused_variables)]
|
||||
#fn main() {
|
||||
enum GeneratorState<Y, R> {
|
||||
Yielded(Y),
|
||||
Complete(R),
|
||||
}
|
||||
@@ -483,8 +479,7 @@ impl Generator for GeneratorA {
|
||||
}
|
||||
}
|
||||
}
|
||||
<span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
#}</code></pre></pre>
|
||||
<p>Remember that our example is the generator we crated which looked like this:</p>
|
||||
<pre><code class="language-rust noplaypen ignore">let mut gen = move || {
|
||||
let to_borrow = String::from("Hello");
|
||||
@@ -511,66 +506,66 @@ does what we'd expect. But there is still one huge problem with this:</p>
|
||||
()
|
||||
};
|
||||
}
|
||||
<span class="boring">enum GeneratorState<Y, 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 {
|
||||
</span><span class="boring"> type Yield;
|
||||
</span><span class="boring"> type Return;
|
||||
</span><span class="boring"> fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum GeneratorA {
|
||||
</span><span class="boring"> Enter,
|
||||
</span><span class="boring"> Yield1 {
|
||||
</span><span class="boring"> to_borrow: String,
|
||||
</span><span class="boring"> borrowed: *const String,
|
||||
</span><span class="boring"> },
|
||||
</span><span class="boring"> Exit,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl GeneratorA {
|
||||
</span><span class="boring"> fn start() -> Self {
|
||||
</span><span class="boring"> GeneratorA::Enter
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">impl Generator for GeneratorA {
|
||||
</span><span class="boring"> type Yield = usize;
|
||||
</span><span class="boring"> type Return = ();
|
||||
</span><span class="boring"> fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
</span><span class="boring"> match self {
|
||||
</span><span class="boring"> GeneratorA::Enter => {
|
||||
</span><span class="boring"> let to_borrow = String::from("Hello");
|
||||
</span><span class="boring"> let borrowed = &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"> // 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"> GeneratorState::Yielded(res)
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> GeneratorA::Yield1 {borrowed, ..} => {
|
||||
</span><span class="boring"> let borrowed: &String = unsafe {&**borrowed};
|
||||
</span><span class="boring"> println!("{} world", borrowed);
|
||||
</span><span class="boring"> *self = GeneratorA::Exit;
|
||||
</span><span class="boring"> GeneratorState::Complete(())
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
#
|
||||
# enum GeneratorA {
|
||||
# Enter,
|
||||
# Yield1 {
|
||||
# to_borrow: String,
|
||||
# borrowed: *const String,
|
||||
# },
|
||||
# Exit,
|
||||
# }
|
||||
#
|
||||
# impl GeneratorA {
|
||||
# fn start() -> Self {
|
||||
# GeneratorA::Enter
|
||||
# }
|
||||
# }
|
||||
# impl Generator for GeneratorA {
|
||||
# type Yield = usize;
|
||||
# type Return = ();
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
# match self {
|
||||
# GeneratorA::Enter => {
|
||||
# let to_borrow = String::from("Hello");
|
||||
# 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);
|
||||
# *self = GeneratorA::Exit;
|
||||
# GeneratorState::Complete(())
|
||||
# }
|
||||
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>The problem is that in safe Rust we can still do this:</p>
|
||||
<p><em>Run the code and compare the results. Do you see the problem?</em></p>
|
||||
<pre><pre class="playpen"><code class="language-rust should_panic"><span class="boring">#![feature(never_type)] // Force nightly compiler to be used in playground
|
||||
</span><span class="boring">// by betting on it's true that this type is named after it's stabilization date...
|
||||
</span>pub fn main() {
|
||||
<pre><pre class="playpen"><code class="language-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...
|
||||
pub fn main() {
|
||||
let mut gen = GeneratorA::start();
|
||||
let mut gen2 = GeneratorA::start();
|
||||
|
||||
@@ -589,61 +584,61 @@ does what we'd expect. But there is still one huge problem with this:</p>
|
||||
()
|
||||
};
|
||||
}
|
||||
<span class="boring">enum GeneratorState<Y, 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 {
|
||||
</span><span class="boring"> type Yield;
|
||||
</span><span class="boring"> type Return;
|
||||
</span><span class="boring"> fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">enum GeneratorA {
|
||||
</span><span class="boring"> Enter,
|
||||
</span><span class="boring"> Yield1 {
|
||||
</span><span class="boring"> to_borrow: String,
|
||||
</span><span class="boring"> borrowed: *const String,
|
||||
</span><span class="boring"> },
|
||||
</span><span class="boring"> Exit,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl GeneratorA {
|
||||
</span><span class="boring"> fn start() -> Self {
|
||||
</span><span class="boring"> GeneratorA::Enter
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">impl Generator for GeneratorA {
|
||||
</span><span class="boring"> type Yield = usize;
|
||||
</span><span class="boring"> type Return = ();
|
||||
</span><span class="boring"> fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
</span><span class="boring"> match self {
|
||||
</span><span class="boring"> GeneratorA::Enter => {
|
||||
</span><span class="boring"> let to_borrow = String::from("Hello");
|
||||
</span><span class="boring"> let borrowed = &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"> // 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"> GeneratorState::Yielded(res)
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> GeneratorA::Yield1 {borrowed, ..} => {
|
||||
</span><span class="boring"> let borrowed: &String = unsafe {&**borrowed};
|
||||
</span><span class="boring"> println!("{} world", borrowed);
|
||||
</span><span class="boring"> *self = GeneratorA::Exit;
|
||||
</span><span class="boring"> GeneratorState::Complete(())
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# enum GeneratorState<Y, R> {
|
||||
# Yielded(Y),
|
||||
# Complete(R),
|
||||
# }
|
||||
#
|
||||
# trait Generator {
|
||||
# type Yield;
|
||||
# type Return;
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
|
||||
# }
|
||||
#
|
||||
# enum GeneratorA {
|
||||
# Enter,
|
||||
# Yield1 {
|
||||
# to_borrow: String,
|
||||
# borrowed: *const String,
|
||||
# },
|
||||
# Exit,
|
||||
# }
|
||||
#
|
||||
# impl GeneratorA {
|
||||
# fn start() -> Self {
|
||||
# GeneratorA::Enter
|
||||
# }
|
||||
# }
|
||||
# impl Generator for GeneratorA {
|
||||
# type Yield = usize;
|
||||
# type Return = ();
|
||||
# fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return> {
|
||||
# match self {
|
||||
# GeneratorA::Enter => {
|
||||
# let to_borrow = String::from("Hello");
|
||||
# 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);
|
||||
# *self = GeneratorA::Exit;
|
||||
# GeneratorState::Complete(())
|
||||
# }
|
||||
# GeneratorA::Exit => panic!("Can't advance an exited generator!"),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>Wait? What happened to "Hello"? And why did our code segfault?</p>
|
||||
<p>Turns out that while the example above compiles just fine, we expose consumers
|
||||
of this this API to both possible undefined behavior and other memory errors
|
||||
@@ -663,7 +658,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("Hello");
|
||||
@@ -688,7 +683,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>
|
||||
@@ -810,18 +805,6 @@ pub fn main() {
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+207
-222
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html" class="active"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html" class="active"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -243,37 +240,37 @@ you see, this works as expected:</p>
|
||||
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> // We need an `init` method to actually set our self-reference
|
||||
</span><span class="boring"> fn init(&mut self) {
|
||||
</span><span class="boring"> let self_ref: *const String = &self.a;
|
||||
</span><span class="boring"> self.b = self_ref;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a(&self) -> &str {
|
||||
</span><span class="boring"> &self.a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b(&self) -> &String {
|
||||
</span><span class="boring"> unsafe {&*(self.b)}
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# use std::pin::Pin;
|
||||
# #[derive(Debug)]
|
||||
# struct Test {
|
||||
# a: String,
|
||||
# b: *const String,
|
||||
# }
|
||||
#
|
||||
# impl Test {
|
||||
# fn new(txt: &str) -> Self {
|
||||
# let a = String::from(txt);
|
||||
# Test {
|
||||
# a,
|
||||
# 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)}
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>In our main method we first instantiate two instances of <code>Test</code> and print out
|
||||
the value of the fields on <code>test1</code>. We get what we'd expect:</p>
|
||||
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||
@@ -293,36 +290,36 @@ which <code>test1</code> is pointing to with the data stored at the memory locat
|
||||
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn init(&mut self) {
|
||||
</span><span class="boring"> let self_ref: *const String = &self.a;
|
||||
</span><span class="boring"> self.b = self_ref;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a(&self) -> &str {
|
||||
</span><span class="boring"> &self.a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b(&self) -> &String {
|
||||
</span><span class="boring"> unsafe {&*(self.b)}
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# use std::pin::Pin;
|
||||
# #[derive(Debug)]
|
||||
# struct Test {
|
||||
# a: String,
|
||||
# b: *const String,
|
||||
# }
|
||||
#
|
||||
# impl Test {
|
||||
# fn new(txt: &str) -> Self {
|
||||
# let a = String::from(txt);
|
||||
# Test {
|
||||
# a,
|
||||
# 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)}
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>Naively, we could think that what we should get a debug print of <code>test1</code> two
|
||||
times like this</p>
|
||||
<pre><code class="language-rust ignore">a: test1, b: test1
|
||||
@@ -349,36 +346,36 @@ be tied to the lifetime of <code>test2</code> anymore.</p>
|
||||
println!("a: {}, b: {}", test2.a(), test2.b());
|
||||
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn init(&mut self) {
|
||||
</span><span class="boring"> let self_ref: *const String = &self.a;
|
||||
</span><span class="boring"> self.b = self_ref;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a(&self) -> &str {
|
||||
</span><span class="boring"> &self.a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b(&self) -> &String {
|
||||
</span><span class="boring"> unsafe {&*(self.b)}
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# use std::pin::Pin;
|
||||
# #[derive(Debug)]
|
||||
# struct Test {
|
||||
# a: String,
|
||||
# b: *const String,
|
||||
# }
|
||||
#
|
||||
# impl Test {
|
||||
# fn new(txt: &str) -> Self {
|
||||
# let a = String::from(txt);
|
||||
# Test {
|
||||
# a,
|
||||
# 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)}
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>That shouldn't happen. There is no serious error yet, but as you can imagine
|
||||
it's easy to create serious bugs using this code.</p>
|
||||
<p>I created a diagram to help visualize what's going on:</p>
|
||||
@@ -445,42 +442,42 @@ we'll show in a second.</p>
|
||||
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
|
||||
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">use std::marker::PhantomPinned;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> // This makes our type `!Unpin`
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
</span><span class="boring"> let self_ptr: *const String = &self.a;
|
||||
</span><span class="boring"> let this = unsafe { self.get_unchecked_mut() };
|
||||
</span><span class="boring"> this.b = self_ptr;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||
</span><span class="boring"> &self.get_ref().a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||
</span><span class="boring"> unsafe { &*(self.b) }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# 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);
|
||||
# Test {
|
||||
# a,
|
||||
# b: std::ptr::null(),
|
||||
# // This makes our type `!Unpin`
|
||||
# _marker: PhantomPinned,
|
||||
# }
|
||||
# }
|
||||
# fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
# let self_ptr: *const String = &self.a;
|
||||
# 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) }
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>Now, if we try to pull the same trick which got us in to trouble the last time
|
||||
you'll get a compilation error.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust compile_fail">pub fn main() {
|
||||
@@ -496,42 +493,42 @@ you'll get a compilation error.</p>
|
||||
std::mem::swap(test1.as_mut(), test2.as_mut());
|
||||
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">use std::marker::PhantomPinned;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> // This makes our type `!Unpin`
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
</span><span class="boring"> let self_ptr: *const String = &self.a;
|
||||
</span><span class="boring"> let this = unsafe { self.get_unchecked_mut() };
|
||||
</span><span class="boring"> this.b = self_ptr;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||
</span><span class="boring"> &self.get_ref().a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||
</span><span class="boring"> unsafe { &*(self.b) }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# 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);
|
||||
# Test {
|
||||
# a,
|
||||
# b: std::ptr::null(),
|
||||
# // This makes our type `!Unpin`
|
||||
# _marker: PhantomPinned,
|
||||
# }
|
||||
# }
|
||||
# fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
# let self_ptr: *const String = &self.a;
|
||||
# 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) }
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>As you see from the error you get by running the code the type system prevents
|
||||
us from swapping the pinned pointers.</p>
|
||||
<blockquote>
|
||||
@@ -552,43 +549,43 @@ after it's initialized like this:</p>
|
||||
mem::swap(&mut test1, &mut test2);
|
||||
println!("Not self referential anymore: {:?}", test1.b);
|
||||
}
|
||||
<span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">use std::marker::PhantomPinned;
|
||||
</span><span class="boring">use std::mem;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">#[derive(Debug)]
|
||||
</span><span class="boring">struct Test {
|
||||
</span><span class="boring"> a: String,
|
||||
</span><span class="boring"> b: *const String,
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Test {
|
||||
</span><span class="boring"> fn new(txt: &str) -> Self {
|
||||
</span><span class="boring"> let a = String::from(txt);
|
||||
</span><span class="boring"> Test {
|
||||
</span><span class="boring"> a,
|
||||
</span><span class="boring"> b: std::ptr::null(),
|
||||
</span><span class="boring"> // This makes our type `!Unpin`
|
||||
</span><span class="boring"> _marker: PhantomPinned,
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
</span><span class="boring"> let self_ptr: *const String = &self.a;
|
||||
</span><span class="boring"> let this = unsafe { self.get_unchecked_mut() };
|
||||
</span><span class="boring"> this.b = self_ptr;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn a<'a>(self: Pin<&'a Self>) -> &'a str {
|
||||
</span><span class="boring"> &self.get_ref().a
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn b<'a>(self: Pin<&'a Self>) -> &'a String {
|
||||
</span><span class="boring"> unsafe { &*(self.b) }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# 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);
|
||||
# Test {
|
||||
# a,
|
||||
# b: std::ptr::null(),
|
||||
# // This makes our type `!Unpin`
|
||||
# _marker: PhantomPinned,
|
||||
# }
|
||||
# }
|
||||
# fn init<'a>(self: Pin<&'a mut Self>) {
|
||||
# let self_ptr: *const String = &self.a;
|
||||
# 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) }
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
</blockquote>
|
||||
<h2><a class="header" href="#pinning-to-the-heap" id="pinning-to-the-heap">Pinning to the heap</a></h2>
|
||||
<p>For completeness let's remove some unsafe and the need for an <code>init</code> method
|
||||
@@ -905,18 +902,6 @@ we want to be able to safely borrow across <code>yield/await</code> points.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+303
-282
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html" class="active"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html" class="active"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -171,9 +168,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},
|
||||
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
||||
};
|
||||
</code></pre>
|
||||
<h2><a class="header" href="#the-executor" id="the-executor">The Executor</a></h2>
|
||||
@@ -264,9 +261,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
|
||||
@@ -306,48 +302,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.
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
@@ -424,6 +429,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 "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 {
|
||||
|
||||
// we need some way of registering a Task with the reactor. Normally this
|
||||
@@ -431,106 +445,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();
|
||||
@@ -543,22 +569,18 @@ and make it sleep for some time which we specify when we create a <code>Task</co
|
||||
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">};
|
||||
</span><span class="boring">
|
||||
</span>fn main() {
|
||||
<pre><pre class="playpen"><code class="language-rust edition2018"># 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
|
||||
# };
|
||||
#
|
||||
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`
|
||||
@@ -596,163 +618,174 @@ 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<F: Future>(mut future: F) -> F::Output {
|
||||
</span><span class="boring"> let mywaker = Arc::new(MyWaker{ thread: thread::current() });
|
||||
</span><span class="boring"> let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
</span><span class="boring"> let mut cx = Context::from_waker(&waker);
|
||||
</span><span class="boring"> let val = loop {
|
||||
</span><span class="boring"> let pinned = unsafe { Pin::new_unchecked(&mut future) };
|
||||
</span><span class="boring"> match Future::poll(pinned, &mut cx) {
|
||||
</span><span class="boring"> Poll::Ready(val) => break val,
|
||||
</span><span class="boring"> Poll::Pending => thread::park(),
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring"> val
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">// ====================== FUTURE IMPLEMENTATION ==============================
|
||||
</span><span class="boring">#[derive(Clone)]
|
||||
</span><span class="boring">struct MyWaker {
|
||||
</span><span class="boring"> thread: thread::Thread,
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</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<Mutex<Reactor>>,
|
||||
</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: &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"> waker_arc.thread.unpark();
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn mywaker_clone(s: &MyWaker) -> RawWaker {
|
||||
</span><span class="boring"> let arc = unsafe { Arc::from_raw(s).clone() };
|
||||
</span><span class="boring"> std::mem::forget(arc.clone()); // increase ref count
|
||||
</span><span class="boring"> RawWaker::new(Arc::into_raw(arc) as *const (), &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(&*(s as *const MyWaker)), // clone
|
||||
</span><span class="boring"> |s| mywaker_wake(&*(s as *const MyWaker)), // wake
|
||||
</span><span class="boring"> |s| mywaker_wake(*(s as *const &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">
|
||||
</span><span class="boring">fn waker_into_waker(s: *const MyWaker) -> Waker {
|
||||
</span><span class="boring"> let raw_waker = RawWaker::new(s as *const (), &VTABLE);
|
||||
</span><span class="boring"> unsafe { Waker::from_raw(raw_waker) }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Task {
|
||||
</span><span class="boring"> fn new(reactor: Arc<Mutex<Reactor>>, data: u64, id: usize) -> 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"> }
|
||||
</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<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
</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"> Poll::Ready(self.id)
|
||||
</span><span class="boring"> } else if self.is_registered {
|
||||
</span><span class="boring"> Poll::Pending
|
||||
</span><span class="boring"> } else {
|
||||
</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">struct Reactor {
|
||||
</span><span class="boring"> dispatcher: Sender<Event>,
|
||||
</span><span class="boring"> handle: Option<JoinHandle<()>>,
|
||||
</span><span class="boring"> readylist: Arc<Mutex<Vec<usize>>>,
|
||||
</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">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Reactor {
|
||||
</span><span class="boring"> fn new() -> Self {
|
||||
</span><span class="boring"> let (tx, rx) = channel::<Event>();
|
||||
</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 handle = thread::spawn(move || {
|
||||
</span><span class="boring"> // This simulates some I/O resource
|
||||
</span><span class="boring"> for event in rx {
|
||||
</span><span class="boring"> println!("REACTOR: {:?}", event);
|
||||
</span><span class="boring"> let rl_clone = rl_clone.clone();
|
||||
</span><span class="boring"> match event {
|
||||
</span><span class="boring"> Event::Close => break,
|
||||
</span><span class="boring"> Event::Timeout(waker, duration, id) => {
|
||||
</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"> });
|
||||
</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"> });
|
||||
</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"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn register(&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"> }
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> fn close(&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(&self, id_to_check: usize) -> 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"> }
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">impl Drop for Reactor {
|
||||
</span><span class="boring"> fn drop(&mut self) {
|
||||
</span><span class="boring"> self.handle.take().map(|h| h.join().unwrap()).unwrap();
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring">}
|
||||
</span></code></pre></pre>
|
||||
# // ============================= EXECUTOR ====================================
|
||||
# fn block_on<F: Future>(mut future: F) -> F::Output {
|
||||
# let mywaker = Arc::new(MyWaker {
|
||||
# thread: thread::current(),
|
||||
# });
|
||||
# let waker = waker_into_waker(Arc::into_raw(mywaker));
|
||||
# let mut cx = Context::from_waker(&waker);
|
||||
#
|
||||
# // SAFETY: we shadow `future` so it can't be accessed again.
|
||||
# let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||
# let val = loop {
|
||||
# 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<Box<Reactor>>>,
|
||||
# data: u64,
|
||||
# }
|
||||
#
|
||||
# fn mywaker_wake(s: &MyWaker) {
|
||||
# let waker_ptr: *const MyWaker = s;
|
||||
# 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) };
|
||||
# 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
|
||||
# )
|
||||
# };
|
||||
#
|
||||
# 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<Box<Reactor>>>, data: u64, id: usize) -> Self {
|
||||
# Task { id, reactor, data }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# impl Future for Task {
|
||||
# type Output = usize;
|
||||
# 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) {
|
||||
# *r.tasks.get_mut(&self.id).unwrap() = TaskState::Finished;
|
||||
# Poll::Ready(self.id)
|
||||
# } else if r.tasks.contains_key(&self.id) {
|
||||
# r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||
# Poll::Pending
|
||||
# } else {
|
||||
# r.register(self.data, cx.waker().clone(), self.id);
|
||||
# Poll::Pending
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# // =============================== REACTOR ===================================
|
||||
# enum TaskState {
|
||||
# Ready,
|
||||
# NotReady(Waker),
|
||||
# Finished,
|
||||
# }
|
||||
# struct Reactor {
|
||||
# dispatcher: Sender<Event>,
|
||||
# handle: Option<JoinHandle<()>>,
|
||||
# tasks: HashMap<usize, TaskState>,
|
||||
# }
|
||||
#
|
||||
# #[derive(Debug)]
|
||||
# enum Event {
|
||||
# Close,
|
||||
# Timeout(u64, usize),
|
||||
# }
|
||||
#
|
||||
# impl Reactor {
|
||||
# fn new() -> Arc<Mutex<Box<Self>>> {
|
||||
# let (tx, rx) = channel::<Event>();
|
||||
# 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 reactor = reactor_clone.clone();
|
||||
# match event {
|
||||
# Event::Close => break,
|
||||
# Event::Timeout(duration, id) => {
|
||||
# let event_handle = thread::spawn(move || {
|
||||
# thread::sleep(Duration::from_secs(duration));
|
||||
# let reactor = reactor.upgrade().unwrap();
|
||||
# reactor.lock().map(|mut r| r.wake(id)).unwrap();
|
||||
# });
|
||||
# handles.push(event_handle);
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# handles.into_iter().for_each(|handle| handle.join().unwrap());
|
||||
# });
|
||||
# reactor.lock().map(|mut r| r.handle = Some(handle)).unwrap();
|
||||
# reactor
|
||||
# }
|
||||
#
|
||||
# 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: 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();
|
||||
# }
|
||||
# }
|
||||
</code></pre></pre>
|
||||
<p>I added a debug printout of the events the reactor registered interest for so we can observe
|
||||
two things:</p>
|
||||
<ol>
|
||||
@@ -874,18 +907,6 @@ do really hope that you do continue to explore further.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html" class="active"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html" class="active"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -155,16 +152,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 +178,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);
|
||||
|
||||
@@ -215,14 +212,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();
|
||||
}
|
||||
|
||||
@@ -234,10 +230,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
|
||||
)
|
||||
};
|
||||
|
||||
@@ -247,97 +243,106 @@ 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) {
|
||||
*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) {
|
||||
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||
Poll::Pending
|
||||
} else {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,18 +427,6 @@ impl Drop for Reactor {
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+80
-65
@@ -16,6 +16,9 @@ function playpen_text(playpen) {
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
// Hide Rust code lines prepended with a specific character
|
||||
var hiding_character = "#";
|
||||
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
@@ -52,15 +55,6 @@ function playpen_text(playpen) {
|
||||
editor.addEventListener("change", function (e) {
|
||||
update_play_button(playpen_block, playground_crates);
|
||||
});
|
||||
// add Ctrl-Enter command to execute rust code
|
||||
editor.commands.addCommand({
|
||||
name: "run",
|
||||
bindKey: {
|
||||
win: "Ctrl-Enter",
|
||||
mac: "Ctrl-Enter"
|
||||
},
|
||||
exec: _editor => run_rust_code(playpen_block)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,59 +161,95 @@ function playpen_text(playpen) {
|
||||
|
||||
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
|
||||
|
||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||
var code_block = block;
|
||||
var pre_block = block.parentNode;
|
||||
// hide lines
|
||||
var lines = code_block.innerHTML.split("\n");
|
||||
var first_non_hidden_line = false;
|
||||
var lines_hidden = false;
|
||||
var trimmed_line = "";
|
||||
|
||||
for (var n = 0; n < lines.length; n++) {
|
||||
trimmed_line = lines[n].trim();
|
||||
if (trimmed_line[0] == hiding_character && trimmed_line[1] != hiding_character) {
|
||||
if (first_non_hidden_line) {
|
||||
lines[n] = "<span class=\"hidden\">" + "\n" + lines[n].replace(/(\s*)# ?/, "$1") + "</span>";
|
||||
}
|
||||
else {
|
||||
lines[n] = "<span class=\"hidden\">" + lines[n].replace(/(\s*)# ?/, "$1") + "\n" + "</span>";
|
||||
}
|
||||
lines_hidden = true;
|
||||
}
|
||||
else if (first_non_hidden_line) {
|
||||
lines[n] = "\n" + lines[n];
|
||||
}
|
||||
else {
|
||||
first_non_hidden_line = true;
|
||||
}
|
||||
if (trimmed_line[0] == hiding_character && trimmed_line[1] == hiding_character) {
|
||||
lines[n] = lines[n].replace("##", "#")
|
||||
}
|
||||
}
|
||||
code_block.innerHTML = lines.join("");
|
||||
|
||||
// If no lines were hidden, return
|
||||
if (!lines.length) { return; }
|
||||
block.classList.add("hide-boring");
|
||||
if (!lines_hidden) { return; }
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
var pre_block = block.parentNode;
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-expand')) {
|
||||
var lines = pre_block.querySelectorAll('span.hidden');
|
||||
|
||||
e.target.classList.remove('fa-expand');
|
||||
e.target.classList.add('fa-compress');
|
||||
e.target.title = 'Hide lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.remove('hide-boring');
|
||||
Array.from(lines).forEach(function (line) {
|
||||
line.classList.remove('hidden');
|
||||
line.classList.add('unhidden');
|
||||
});
|
||||
} else if (e.target.classList.contains('fa-compress')) {
|
||||
var lines = pre_block.querySelectorAll('span.unhidden');
|
||||
|
||||
e.target.classList.remove('fa-compress');
|
||||
e.target.classList.add('fa-expand');
|
||||
e.target.title = 'Show hidden lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.add('hide-boring');
|
||||
Array.from(lines).forEach(function (line) {
|
||||
line.classList.remove('unhidden');
|
||||
line.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.playpen_copyable) {
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playpen')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playpen')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
|
||||
// Process playpen code blocks
|
||||
Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) {
|
||||
@@ -237,21 +267,19 @@ function playpen_text(playpen) {
|
||||
runCodeButton.title = 'Run this code';
|
||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
|
||||
if (window.playpen_copyable) {
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
}
|
||||
|
||||
let code_block = pre_block.querySelector("code");
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
var undoChangesButton = document.createElement('button');
|
||||
@@ -293,7 +321,7 @@ function playpen_text(playpen) {
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function set_theme(theme, store = true) {
|
||||
function set_theme(theme) {
|
||||
let ace_theme;
|
||||
|
||||
if (theme == 'coal' || theme == 'navy') {
|
||||
@@ -328,10 +356,9 @@ function playpen_text(playpen) {
|
||||
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
|
||||
|
||||
if (store) {
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
}
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
|
||||
document.body.className = theme;
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
}
|
||||
@@ -341,7 +368,7 @@ function playpen_text(playpen) {
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
|
||||
set_theme(theme, false);
|
||||
set_theme(theme);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
@@ -363,7 +390,7 @@ function playpen_text(playpen) {
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang-nursery/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
@@ -408,7 +435,6 @@ function playpen_text(playpen) {
|
||||
(function sidebar() {
|
||||
var html = document.querySelector("html");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var sidebarScrollBox = document.getElementById("sidebar-scrollbox");
|
||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
@@ -425,17 +451,6 @@ function playpen_text(playpen) {
|
||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||
}
|
||||
|
||||
|
||||
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
||||
|
||||
function toggleSection(ev) {
|
||||
ev.currentTarget.parentElement.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
||||
el.addEventListener('click', toggleSection);
|
||||
});
|
||||
|
||||
function hideSidebar() {
|
||||
html.classList.remove('sidebar-visible')
|
||||
html.classList.add('sidebar-hidden');
|
||||
@@ -507,7 +522,7 @@ function playpen_text(playpen) {
|
||||
// Scroll sidebar to current active section
|
||||
var activeSection = sidebar.querySelector(".active");
|
||||
if (activeSection) {
|
||||
sidebarScrollBox.scrollTop = activeSection.offsetTop;
|
||||
sidebar.scrollTop = activeSection.offsetTop;
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -600,6 +615,6 @@ function playpen_text(playpen) {
|
||||
menu.classList.remove('bordered');
|
||||
}
|
||||
|
||||
previousScrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
||||
previousScrollTop = document.scrollingElement.scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
|
||||
+7
-22
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html" class="active">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html" class="active">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -267,18 +264,6 @@ linked to in the book, here are some of my suggestions:</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+12
-45
@@ -8,9 +8,7 @@
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--bg);
|
||||
}
|
||||
|
||||
#searchresults a,
|
||||
.content a:link,
|
||||
a:visited,
|
||||
@@ -45,7 +43,7 @@ a > .hljs {
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
z-index: 10;
|
||||
line-height: var(--menu-bar-height);
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
@@ -73,7 +71,7 @@ a > .hljs {
|
||||
}
|
||||
|
||||
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
|
||||
transform: translateY(calc(-10px - var(--menu-bar-height)));
|
||||
transform: translateY(-60px);
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
@@ -87,8 +85,8 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
font-size: 2rem;
|
||||
line-height: var(--menu-bar-height);
|
||||
font-size: 20px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
@@ -126,7 +124,7 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
top: 50px; /* Height of menu-bar */
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
@@ -137,14 +135,10 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.nav-chapters:hover {
|
||||
text-decoration: none;
|
||||
background-color: var(--theme-hover);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
}
|
||||
.nav-chapters:hover { text-decoration: none; }
|
||||
|
||||
.nav-wrapper {
|
||||
margin-top: 50px;
|
||||
@@ -182,7 +176,8 @@ html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-conta
|
||||
/* Inline code */
|
||||
|
||||
:not(pre) > .hljs {
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -375,13 +370,7 @@ ul#searchresults span.teaser em {
|
||||
padding-left: 0;
|
||||
line-height: 2.2em;
|
||||
}
|
||||
|
||||
.chapter ol {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter li {
|
||||
display: flex;
|
||||
color: var(--sidebar-non-existant);
|
||||
}
|
||||
.chapter li a {
|
||||
@@ -395,32 +384,10 @@ ul#searchresults span.teaser em {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li a.active {
|
||||
.chapter li .active {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li > a.toggle {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.chapter li > a.toggle div {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
/* collapse the section */
|
||||
.chapter li:not(.expanded) + li > ol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chapter li.expanded > a.toggle div {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
@@ -446,7 +413,7 @@ ul#searchresults span.teaser em {
|
||||
.theme-popup {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: var(--menu-bar-height);
|
||||
top: 50px;
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
|
||||
+3
-19
@@ -2,11 +2,6 @@
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
:root {
|
||||
/* Browser default font-size is 16px, this way 1 rem = 10px */
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: var(--fg);
|
||||
@@ -16,20 +11,19 @@ html {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
font-size: 1rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
|
||||
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
.boring { opacity: 0.6; }
|
||||
.hide-boring .boring { display: none; }
|
||||
.hidden { display: none; }
|
||||
.play-button.hidden { display: none; }
|
||||
|
||||
h2, h3 { margin-top: 2.5em; }
|
||||
h4, h5 { margin-top: 2em; }
|
||||
@@ -50,13 +44,6 @@ h4 a.header:target::before {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
h1 a.header:target,
|
||||
h2 a.header:target,
|
||||
h3 a.header:target,
|
||||
h4 a.header:target {
|
||||
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
|
||||
}
|
||||
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 var(--page-padding);
|
||||
@@ -105,9 +92,6 @@ table thead td {
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
}
|
||||
table thead th {
|
||||
padding: 3px 20px;
|
||||
}
|
||||
table thead tr {
|
||||
border: 1px var(--table-header-bg) solid;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
--sidebar-width: 300px;
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
--menu-bar-height: 50px;
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
@@ -209,45 +208,3 @@
|
||||
--searchresults-li-bg: #dec2a2;
|
||||
--search-mark-bg: #e69f67;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.light.no-js {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -6,14 +6,12 @@ window.editors = [];
|
||||
}
|
||||
|
||||
Array.from(document.querySelectorAll('.editable')).forEach(function(editable) {
|
||||
let display_line_numbers = window.playpen_line_numbers || false;
|
||||
|
||||
let editor = ace.edit(editable);
|
||||
editor.setOptions({
|
||||
highlightActiveLine: false,
|
||||
showPrintMargin: false,
|
||||
showLineNumbers: display_line_numbers,
|
||||
showGutter: display_line_numbers,
|
||||
showLineNumbers: false,
|
||||
showGutter: false,
|
||||
maxLines: Infinity,
|
||||
fontSize: "0.875em" // please adjust the font size of the code in general.css
|
||||
});
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+7
-30
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -212,10 +209,6 @@ read the finished product, but a big thanks is definitely due.</p>
|
||||
|
||||
|
||||
|
||||
<a rel="next" href="0_background_information.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
@@ -226,10 +219,6 @@ read the finished product, but a big thanks is definitely due.</p>
|
||||
|
||||
|
||||
|
||||
<a href="0_background_information.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
@@ -271,18 +260,6 @@ read the finished product, but a big thanks is definitely due.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+7
-22
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="sidebar-visible no-js light">
|
||||
<html lang="en" class="sidebar-visible no-js">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
@@ -32,11 +32,11 @@
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="light">
|
||||
<!-- Provide site root to javascript -->
|
||||
<script type="text/javascript">
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
var default_theme = "light";
|
||||
</script>
|
||||
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
@@ -60,11 +60,8 @@
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
var html = document.querySelector('html');
|
||||
html.classList.remove('no-js')
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add('js');
|
||||
document.body.className = theme;
|
||||
document.querySelector('html').className = theme + ' js';
|
||||
</script>
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
@@ -80,8 +77,8 @@
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="expanded affix "><a href="introduction.html" class="active">Introduction</a></li><li class="expanded "><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li class="expanded "><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li class="expanded "><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li class="expanded "><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li class="expanded "><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li class="expanded "><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li class="expanded "><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="expanded affix "><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
<div class="sidebar-scrollbox">
|
||||
<ol class="chapter"><li class="affix"><a href="introduction.html" class="active">Introduction</a></li><li><a href="0_background_information.html"><strong aria-hidden="true">1.</strong> Background information</a></li><li><a href="1_futures_in_rust.html"><strong aria-hidden="true">2.</strong> Futures in Rust</a></li><li><a href="2_waker_context.html"><strong aria-hidden="true">3.</strong> Waker and Context</a></li><li><a href="3_generators_async_await.html"><strong aria-hidden="true">4.</strong> Generators and async/await</a></li><li><a href="4_pin.html"><strong aria-hidden="true">5.</strong> Pin</a></li><li><a href="6_future_example.html"><strong aria-hidden="true">6.</strong> Implementing Futures</a></li><li><a href="8_finished_example.html"><strong aria-hidden="true">7.</strong> Finished example (editable)</a></li><li class="affix"><a href="conclusion.html">Conclusion and exercises</a></li></ol>
|
||||
</div>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||||
</nav>
|
||||
@@ -271,18 +268,6 @@ read the finished product, but a big thanks is definitely due.</p>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_line_numbers = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
window.playpen_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
+908
-881
File diff suppressed because it is too large
Load Diff
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+188
-152
@@ -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},
|
||||
future::Future, pin::Pin, sync::{ mpsc::{channel, Sender}, Arc, Mutex,},
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}
|
||||
thread::{self, JoinHandle}, time::{Duration, Instant}, collections::HashMap
|
||||
};
|
||||
```
|
||||
|
||||
@@ -131,9 +131,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 +172,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.
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -303,6 +311,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 +329,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,9 +459,9 @@ 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() {
|
||||
@@ -441,10 +470,6 @@ fn main() {
|
||||
|
||||
// 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,15 +507,18 @@ 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(),
|
||||
# };
|
||||
@@ -507,29 +535,28 @@ fn main() {
|
||||
# #[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
|
||||
# )
|
||||
# };
|
||||
#
|
||||
@@ -539,97 +566,106 @@ fn main() {
|
||||
# }
|
||||
#
|
||||
# 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) {
|
||||
# *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) {
|
||||
# r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||
# Poll::Pending
|
||||
# } else {
|
||||
# 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 {
|
||||
# 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)
|
||||
# }
|
||||
# }
|
||||
#
|
||||
|
||||
+64
-56
@@ -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,106 @@ 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) {
|
||||
*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) {
|
||||
r.tasks.insert(self.id, TaskState::NotReady(cx.waker().clone()));
|
||||
Poll::Pending
|
||||
} else {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user