diff --git a/README.md b/README.md index bb640e6..0b7aac3 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/book/0_background_information.html b/book/0_background_information.html index 1bd86bb..051deaf 100644 --- a/book/0_background_information.html +++ b/book/0_background_information.html @@ -1,5 +1,5 @@ - + @@ -32,11 +32,11 @@ - + @@ -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'; @@ -80,8 +77,8 @@ @@ -280,199 +277,199 @@ It's not in any way meant to showcase "best practice". Just so we're o the same page.

Press the expand icon in the top right corner to show the example code.

-
#![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() {
+
# #![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.

}); runtime.run(); } -#[cfg(windows)] -fn main() { } -
+# #[cfg(windows)] +# fn main() { } +

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.

@@ -738,18 +735,6 @@ need to be polled once before they do any work.

- - - - - - - - diff --git a/book/1_futures_in_rust.html b/book/1_futures_in_rust.html index b160214..e01dacf 100644 --- a/book/1_futures_in_rust.html +++ b/book/1_futures_in_rust.html @@ -1,5 +1,5 @@ - + @@ -32,11 +32,11 @@ - + @@ -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'; @@ -80,8 +77,8 @@ @@ -197,7 +194,7 @@ a runtime, but we'll go through how they're constructed in this book as well.

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.

Non-leaf-futures

-

Non-leaf-futures is the kind of futures we as users of a runtime writes +

Non-leaf-futures is the kind of futures we as users of a runtime write ourselves using the async keyword to create a task which can be run on the executor.

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.

notifying a Future that it can do more work, and actually doing the work on the Future.

You can think of the former as the reactor's job, and the latter as the -executors job. These two parts of a runtime interacts using the Waker type.

+executors job. These two parts of a runtime interact with each other using the Waker type.

The two most popular runtimes for Futures as of writing this is: