From ab3439e62a0180141bdccdfd8f53109338c7244d Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Sun, 6 Sep 2020 17:33:33 -0400 Subject: [PATCH 01/10] add an id to each executor This is an ergonomics effort, so that we can easily know which executor we are executing on. --- scipio/src/executor.rs | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/scipio/src/executor.rs b/scipio/src/executor.rs index 24576f321..584b03c39 100644 --- a/scipio/src/executor.rs +++ b/scipio/src/executor.rs @@ -34,6 +34,7 @@ use std::future::Future; use std::io; use std::pin::Pin; use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; @@ -46,6 +47,8 @@ use crate::task::waker_fn::waker_fn; use crate::Reactor; use crate::{IoRequirements, Latency}; +static EXECUTOR_ID: AtomicUsize = AtomicUsize::new(0); + #[derive(Debug, Clone)] /// Error thrown when a Task Queue is not found. pub struct QueueNotFoundError { @@ -304,6 +307,7 @@ pub struct LocalExecutor { queues: Rc>, parker: parking::Parker, binding: Option, + id: usize, } impl LocalExecutor { @@ -326,6 +330,7 @@ impl LocalExecutor { queues: ExecutorQueues::new(), parker: p, binding, + id: EXECUTOR_ID.fetch_add(1, Ordering::Relaxed), }; if let Some(cpu) = binding { @@ -346,6 +351,19 @@ impl LocalExecutor { Ok(le) } + /// Returns a unique identifier for this Executor. + /// + /// # Examples + /// ``` + /// use scipio::LocalExecutor; + /// + /// let local_ex = LocalExecutor::new(None).expect("failed to create local executor"); + /// println!("My ID: {}", local_ex.id()); + /// ``` + pub fn id(&self) -> usize { + self.id + } + /// Creates a task queue in the executor. /// /// Returns an opaque handler that can later be used to launch tasks into that queue with spawn_into @@ -717,6 +735,34 @@ impl Task { } } + /// Returns the id of the current executor + /// + /// If called from a [`LocalExecutor`], returns the id of the executor. + /// + /// Otherwise, this method panics. + /// + /// # Examples + /// + /// ``` + /// use scipio::{LocalExecutor, Task}; + /// + /// let local_ex = LocalExecutor::new(None).expect("failed to create local executor"); + /// + /// local_ex.run(async { + /// println!("my ID: {}", Task::<()>::id()); + /// }); + /// ``` + pub fn id() -> usize + where + T: 'static, + { + if LOCAL_EX.is_set() { + LOCAL_EX.with(|local_ex| local_ex.id()) + } else { + panic!("`Task::id()` must be called from a `LocalExecutor`") + } + } + /// Detaches the task to let it keep running in the background. /// /// # Examples From 6de6973bbfb32f393e8c425523ed3e29417bd442 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Sun, 6 Sep 2020 17:46:11 -0400 Subject: [PATCH 02/10] ergonomics: provide Local, a nicer way to write Task::<()>:: Some functions that access the executor are available through Task, which allow us to access the local executor without having a reference to it (through its local thread local variable). However Task's main role is to spawn a new task and store its result. So it requires a type generic parameter, T. For helper functions that don't spawn we have to write Task::<()>:: which is a hassle. This patch adds Local, an alias to Task::<()>:: --- scipio/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scipio/src/lib.rs b/scipio/src/lib.rs index fa3dd01a3..83b26fd91 100644 --- a/scipio/src/lib.rs +++ b/scipio/src/lib.rs @@ -144,6 +144,17 @@ pub use crate::pollable::Async; pub use crate::sys::DmaBuffer; pub use crate::timer::Timer; +/// Local is an ergonomic way to access the local executor. +/// The local is executed through a Task type, but the Task type has a type +/// parameter consisting of the return type of the future encapsulated by this +/// task. +/// +/// However for associated functions without a self parameter, like local() and +/// local_into(), the type is always () and Rust is not able to ellide. +/// +/// Writing Task::<()>::function() works, but it is not very ergonomic. +pub type Local = Task<()>; + /// An attribute of a TaskQueue, passed during its creation. /// /// This tells the executor whether or not tasks in this class are latency From 1507cbd32895b542b09a61de899ebedaeb00bdfd Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Sun, 6 Sep 2020 19:29:03 -0400 Subject: [PATCH 03/10] spawn_new: an ergonomic way to spawn a new executor. It is easy to spawn an executor in the current thread: all we have to do is create it. However, there are two main problems with it: 1. The future that we create initially does not live in a task queue. This is pretty much a requirement, because the run() function just executes a future to completion. There are real problems arising from the fact that this future is not on a task queue. For example, we can't call later().await on it (as what that does is move ourselves to the back of the task queue). 2. The potential of a thread-per-core system is only realized when there are many executors, one per each CPU. That means that the user now has to create thread, keep them alive, create an executor inside each thread, etc. Not fun. To make this code more ergonomic, we will create the function LocalExecutor::spawn_new(). It will create a thread and spawn an executor inside it, making the creation of executors easier. The future that the caller passed is executed inside a task queue, meaning every Scipio function will work. And he future that we actually pass to run, is the result of draining that task queue. When the user is done, the function wait_on_executors() can be called to wait on all the executors created this way at once. Like so: LocalExecutor::spawn_new("hello", Some(0), async move { function_on_cpu0().await; }); for i in 1..N { LocalExecutor::spawn_new("hello", Some(i), async move { function_on_cpu_aux(i).await; }); } LocalExecutor::wait_on_executors(); The next step in ergonomics for this is having a single entry point that creates N executors and wait on them. However it is a bit hard to figure out the details of how this should look like without having inter-executor channels for communications. So we will defer. --- LICENSE-3rdparty.csv | 1 + scipio/Cargo.toml | 1 + scipio/src/executor.rs | 113 +++++++++++++++++++++++++++++++++++------ scipio/src/lib.rs | 2 + 4 files changed, 102 insertions(+), 15 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 50907cc06..a3c7c065c 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -16,3 +16,4 @@ uring-sys,https://crates.io/crates/uring-sys,MIT/Apache-2.0,Without Boats iou,https://crates.io/crates/iou,MIT/Apache-2.0,Without Boats bitmaps,https://crates.io/crates/bitmaps,MPL-2.0,Bodil Stokke rlimit,https://crates.io/crates/rlimit,MIT,Nugine +lazy-static,https://crates.io/crates/lazy_static,MIT/Apache-2.0,Marvin Löbel diff --git a/scipio/Cargo.toml b/scipio/Cargo.toml index 6e002a25c..fa6762129 100644 --- a/scipio/Cargo.toml +++ b/scipio/Cargo.toml @@ -27,3 +27,4 @@ typenum = "1.12" scoped-tls = "1.0.0" futures = "0.3.5" rlimit = "0.3.0" +lazy_static = "1.4.0" diff --git a/scipio/src/executor.rs b/scipio/src/executor.rs index 584b03c39..e1834033c 100644 --- a/scipio/src/executor.rs +++ b/scipio/src/executor.rs @@ -35,8 +35,11 @@ use std::io; use std::pin::Pin; use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Mutex; use std::task::{Context, Poll}; +use std::thread::{Builder, JoinHandle}; use std::time::{Duration, Instant}; +use std::vec::Vec; use futures_lite::pin; use scoped_tls::scoped_thread_local; @@ -49,6 +52,10 @@ use crate::{IoRequirements, Latency}; static EXECUTOR_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static! { + static ref SPAWNED_EXECUTORS: Mutex>> = Mutex::new(Vec::new()); +} + #[derive(Debug, Clone)] /// Error thrown when a Task Queue is not found. pub struct QueueNotFoundError { @@ -311,6 +318,25 @@ pub struct LocalExecutor { } impl LocalExecutor { + fn init(&mut self) -> io::Result<()> { + if let Some(cpu) = self.binding { + bind_to_cpu(cpu)?; + } + + let queues = self.queues.clone(); + let index = 0; + + let io_requirements = IoRequirements::new(Latency::NotImportant, 0); + self.queues.borrow_mut().available_executors.insert( + 0, + TaskQueue::new("default", 1000, io_requirements, move || { + let mut queues = queues.borrow_mut(); + queues.maybe_activate(index); + }), + ); + Ok(()) + } + /// Creates a single-threaded executor, optionally bound to a specific CPU /// /// # Examples @@ -326,29 +352,86 @@ impl LocalExecutor { /// ``` pub fn new(binding: Option) -> io::Result { let p = parking::Parker::new(); - let le = LocalExecutor { + let mut le = LocalExecutor { queues: ExecutorQueues::new(), parker: p, binding, id: EXECUTOR_ID.fetch_add(1, Ordering::Relaxed), }; - if let Some(cpu) = binding { - bind_to_cpu(cpu)?; - } + le.init()?; + Ok(le) + } - let queues = le.queues.clone(); - let index = 0; + /// Creates a single-threaded executor, optionally bound to a specific CPU, inside + /// a newly craeted thread. The parameter `name` specifies the name of the thread. + /// + /// This is a more ergonomic way to create a thread and then run an executor inside it + /// This function panics if creating the thread or the executor fails. If you need more + /// fine-grained error handling consider initializing those entities manually. + /// + /// You must call wait_on_executors() to wait for all executors spawned through spawn_new + /// to return + /// + /// # Examples + /// + /// ``` + /// use scipio::LocalExecutor; + /// + /// // executor is a single thread, but not bound to any particular CPU. + /// LocalExecutor::spawn_new("myname", None, async move { + /// println!("hello"); + /// }); + /// + /// LocalExecutor::wait_on_executors(); + /// ``` + pub fn spawn_new(name: &'static str, binding: Option, fut: F) + where + F: Send + 'static, + F: Future, + T: Send + 'static, + { + let id = EXECUTOR_ID.fetch_add(1, Ordering::Relaxed); + + let mut executors = SPAWNED_EXECUTORS.lock().unwrap(); + (*executors).push( + Builder::new() + .name(format!("{}-{}", name, id).to_string()) + .spawn(move || { + let mut le = LocalExecutor { + queues: ExecutorQueues::new(), + parker: parking::Parker::new(), + binding, + id, + }; + le.init().unwrap(); + le.run(async move { + let task = Task::local(async move { + fut.await; + }); + task.await; + }) + }) + .unwrap(), + ) + } - let io_requirements = IoRequirements::new(Latency::NotImportant, 0); - le.queues.borrow_mut().available_executors.insert( - 0, - TaskQueue::new("default", 1000, io_requirements, move || { - let mut queues = queues.borrow_mut(); - queues.maybe_activate(index); - }), - ); - Ok(le) + /// Wait for all executors called through spawn_new to return + /// + /// # Examples + /// use scipio::LocalExecutor; + /// + /// // executor is a single thread, but not bound to any particular CPU. + /// LocalExecutor::spawn_new("myname", None, async move { + /// println!("hello"); + /// }); + /// + /// LocalExecutor::wait_on_executors(); + pub fn wait_on_executors() { + let mut executors = SPAWNED_EXECUTORS.lock().unwrap(); + for ex in std::mem::replace(&mut *executors, Vec::new()) { + ex.join().unwrap(); + } } /// Returns a unique identifier for this Executor. diff --git a/scipio/src/lib.rs b/scipio/src/lib.rs index 83b26fd91..1b6ff71e4 100644 --- a/scipio/src/lib.rs +++ b/scipio/src/lib.rs @@ -40,6 +40,8 @@ #[macro_use] extern crate nix; extern crate alloc; +#[macro_use] +extern crate lazy_static; use crate::parking::Reactor; use std::fmt::Debug; From da0c3a938518fb24da57197cf2f58b9859e3d3d8 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Sun, 6 Sep 2020 19:34:38 -0400 Subject: [PATCH 04/10] add a simple example of how to use scipio This is already using the newly added ergonomic functions. --- scipio/examples/hello_world.rs | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 scipio/examples/hello_world.rs diff --git a/scipio/examples/hello_world.rs b/scipio/examples/hello_world.rs new file mode 100644 index 000000000..4a7983843 --- /dev/null +++ b/scipio/examples/hello_world.rs @@ -0,0 +1,43 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the +// MIT/Apache-2.0 License, at your convenience +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2020 Datadog, Inc. +// +use futures::future::join_all; +use scipio::{Local, LocalExecutor}; + +async fn hello() { + let mut tasks = vec![]; + for t in 0..5 { + tasks.push(Local::local(async move { + println!("{}: Hello {} ...", Local::id(), t); + Local::later().await; + println!("{}: ... {} World!", Local::id(), t); + })); + } + join_all(tasks).await; +} + +fn main() { + // There are two ways to create an executor, demonstrated in this example. + // + // We can create it in the current thread, and run it separately later... + let ex = LocalExecutor::new(Some(0)).unwrap(); + + // Or we can spawn a new thread with an executor inside. + LocalExecutor::spawn_new("hello", Some(1), async move { + hello().await; + }); + + // If you create the executor manually, you have to run it like so. + // + // spawn_new() is the preferred way to create an executor! + ex.run(async move { + hello().await; + }); + + // This waits for all executors called through spawn_new to return. + // Note that the executor created through new() is not waited here. + // But because run() is synchronous, that is not needed. + LocalExecutor::wait_on_executors(); +} From cdb277445456cd1530f080499f5743d6b64b608f Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 09:28:31 -0400 Subject: [PATCH 05/10] Fixes for executor - spawn_new renamed to spawn_executor - no longer provide a helper to join spawned executors --- LICENSE-3rdparty.csv | 1 - scipio/Cargo.toml | 1 - scipio/src/executor.rs | 75 ++++++++++++++---------------------------- scipio/src/lib.rs | 2 -- 4 files changed, 25 insertions(+), 54 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index a3c7c065c..50907cc06 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -16,4 +16,3 @@ uring-sys,https://crates.io/crates/uring-sys,MIT/Apache-2.0,Without Boats iou,https://crates.io/crates/iou,MIT/Apache-2.0,Without Boats bitmaps,https://crates.io/crates/bitmaps,MPL-2.0,Bodil Stokke rlimit,https://crates.io/crates/rlimit,MIT,Nugine -lazy-static,https://crates.io/crates/lazy_static,MIT/Apache-2.0,Marvin Löbel diff --git a/scipio/Cargo.toml b/scipio/Cargo.toml index fa6762129..6e002a25c 100644 --- a/scipio/Cargo.toml +++ b/scipio/Cargo.toml @@ -27,4 +27,3 @@ typenum = "1.12" scoped-tls = "1.0.0" futures = "0.3.5" rlimit = "0.3.0" -lazy_static = "1.4.0" diff --git a/scipio/src/executor.rs b/scipio/src/executor.rs index e1834033c..b6080a4cb 100644 --- a/scipio/src/executor.rs +++ b/scipio/src/executor.rs @@ -35,11 +35,9 @@ use std::io; use std::pin::Pin; use std::rc::Rc; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; use std::task::{Context, Poll}; use std::thread::{Builder, JoinHandle}; use std::time::{Duration, Instant}; -use std::vec::Vec; use futures_lite::pin; use scoped_tls::scoped_thread_local; @@ -52,10 +50,6 @@ use crate::{IoRequirements, Latency}; static EXECUTOR_ID: AtomicUsize = AtomicUsize::new(0); -lazy_static! { - static ref SPAWNED_EXECUTORS: Mutex>> = Mutex::new(Vec::new()); -} - #[derive(Debug, Clone)] /// Error thrown when a Task Queue is not found. pub struct QueueNotFoundError { @@ -370,8 +364,6 @@ impl LocalExecutor { /// This function panics if creating the thread or the executor fails. If you need more /// fine-grained error handling consider initializing those entities manually. /// - /// You must call wait_on_executors() to wait for all executors spawned through spawn_new - /// to return /// /// # Examples /// @@ -379,13 +371,18 @@ impl LocalExecutor { /// use scipio::LocalExecutor; /// /// // executor is a single thread, but not bound to any particular CPU. - /// LocalExecutor::spawn_new("myname", None, async move { + /// let handle = LocalExecutor::spawn_executor("myname", None, async move { /// println!("hello"); - /// }); + /// }).unwrap(); /// - /// LocalExecutor::wait_on_executors(); + /// handle.join().unwrap(); /// ``` - pub fn spawn_new(name: &'static str, binding: Option, fut: F) + #[must_use = "This spawns an executor on a thread, so you must acquire its handle and then join() to keep it alive"] + pub fn spawn_executor( + name: &'static str, + binding: Option, + fut: F, + ) -> io::Result> where F: Send + 'static, F: Future, @@ -393,45 +390,23 @@ impl LocalExecutor { { let id = EXECUTOR_ID.fetch_add(1, Ordering::Relaxed); - let mut executors = SPAWNED_EXECUTORS.lock().unwrap(); - (*executors).push( - Builder::new() - .name(format!("{}-{}", name, id).to_string()) - .spawn(move || { - let mut le = LocalExecutor { - queues: ExecutorQueues::new(), - parker: parking::Parker::new(), - binding, - id, - }; - le.init().unwrap(); - le.run(async move { - let task = Task::local(async move { - fut.await; - }); - task.await; - }) + Builder::new() + .name(format!("{}-{}", name, id).to_string()) + .spawn(move || { + let mut le = LocalExecutor { + queues: ExecutorQueues::new(), + parker: parking::Parker::new(), + binding, + id, + }; + le.init().unwrap(); + le.run(async move { + let task = Task::local(async move { + fut.await; + }); + task.await; }) - .unwrap(), - ) - } - - /// Wait for all executors called through spawn_new to return - /// - /// # Examples - /// use scipio::LocalExecutor; - /// - /// // executor is a single thread, but not bound to any particular CPU. - /// LocalExecutor::spawn_new("myname", None, async move { - /// println!("hello"); - /// }); - /// - /// LocalExecutor::wait_on_executors(); - pub fn wait_on_executors() { - let mut executors = SPAWNED_EXECUTORS.lock().unwrap(); - for ex in std::mem::replace(&mut *executors, Vec::new()) { - ex.join().unwrap(); - } + }) } /// Returns a unique identifier for this Executor. diff --git a/scipio/src/lib.rs b/scipio/src/lib.rs index 1b6ff71e4..83b26fd91 100644 --- a/scipio/src/lib.rs +++ b/scipio/src/lib.rs @@ -40,8 +40,6 @@ #[macro_use] extern crate nix; extern crate alloc; -#[macro_use] -extern crate lazy_static; use crate::parking::Reactor; use std::fmt::Debug; From a39909e5d4ce611b0527729a50e9eee86830ff6a Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 09:28:13 -0400 Subject: [PATCH 06/10] adjustments for the hello world example --- scipio/examples/hello_world.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scipio/examples/hello_world.rs b/scipio/examples/hello_world.rs index 4a7983843..3d5490156 100644 --- a/scipio/examples/hello_world.rs +++ b/scipio/examples/hello_world.rs @@ -5,6 +5,7 @@ // use futures::future::join_all; use scipio::{Local, LocalExecutor}; +use std::io::Result; async fn hello() { let mut tasks = vec![]; @@ -18,16 +19,16 @@ async fn hello() { join_all(tasks).await; } -fn main() { +fn main() -> Result<()> { // There are two ways to create an executor, demonstrated in this example. // // We can create it in the current thread, and run it separately later... - let ex = LocalExecutor::new(Some(0)).unwrap(); + let ex = LocalExecutor::new(Some(0))?; // Or we can spawn a new thread with an executor inside. - LocalExecutor::spawn_new("hello", Some(1), async move { + let handle = LocalExecutor::spawn_executor("hello", Some(1), async move { hello().await; - }); + })?; // If you create the executor manually, you have to run it like so. // @@ -36,8 +37,8 @@ fn main() { hello().await; }); - // This waits for all executors called through spawn_new to return. - // Note that the executor created through new() is not waited here. - // But because run() is synchronous, that is not needed. - LocalExecutor::wait_on_executors(); + // The newly spawned executor runs on a thread, so we need to join on + // its handle so we can wait for it to finish + handle.join().unwrap(); + Ok(()) } From 2e7394797f0b32fa53cb844a585cf99e1e4493ab Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 12:19:03 -0400 Subject: [PATCH 07/10] executor: pass a function rather than a future The main difference in usage is that now instead of writing "async move" we write "|| async move" This is because we can't really require this future to be Send, or it starts to requiring everything down the line to be Send too. But we still don't want to just lift the requirement. So we require FnOnce + Send. --- scipio/src/executor.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scipio/src/executor.rs b/scipio/src/executor.rs index b6080a4cb..3f5f56ce1 100644 --- a/scipio/src/executor.rs +++ b/scipio/src/executor.rs @@ -378,15 +378,14 @@ impl LocalExecutor { /// handle.join().unwrap(); /// ``` #[must_use = "This spawns an executor on a thread, so you must acquire its handle and then join() to keep it alive"] - pub fn spawn_executor( + pub fn spawn_executor( name: &'static str, binding: Option, - fut: F, + fut_gen: G, ) -> io::Result> where - F: Send + 'static, - F: Future, - T: Send + 'static, + G: FnOnce() -> F + std::marker::Send + 'static, + F: Future + 'static, { let id = EXECUTOR_ID.fetch_add(1, Ordering::Relaxed); @@ -402,7 +401,7 @@ impl LocalExecutor { le.init().unwrap(); le.run(async move { let task = Task::local(async move { - fut.await; + fut_gen().await; }); task.await; }) From a2fe8ebd6a3aceabf0005c28a833a2e8347d91ca Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 13:16:23 -0400 Subject: [PATCH 08/10] adjust the hello world example to the new executor syntax --- scipio/examples/hello_world.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipio/examples/hello_world.rs b/scipio/examples/hello_world.rs index 3d5490156..3f01be002 100644 --- a/scipio/examples/hello_world.rs +++ b/scipio/examples/hello_world.rs @@ -26,7 +26,7 @@ fn main() -> Result<()> { let ex = LocalExecutor::new(Some(0))?; // Or we can spawn a new thread with an executor inside. - let handle = LocalExecutor::spawn_executor("hello", Some(1), async move { + let handle = LocalExecutor::spawn_executor("hello", Some(1), || async move { hello().await; })?; From 5b4f4fcbb870fc8927a37186c67b558794f66e41 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 15:28:49 -0400 Subject: [PATCH 09/10] ellide -> elide --- scipio/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipio/src/lib.rs b/scipio/src/lib.rs index 83b26fd91..7f959a0f2 100644 --- a/scipio/src/lib.rs +++ b/scipio/src/lib.rs @@ -150,7 +150,7 @@ pub use crate::timer::Timer; /// task. /// /// However for associated functions without a self parameter, like local() and -/// local_into(), the type is always () and Rust is not able to ellide. +/// local_into(), the type is always () and Rust is not able to elide. /// /// Writing Task::<()>::function() works, but it is not very ergonomic. pub type Local = Task<()>; From 6db555eb1c927e13de2455a890d4e0f1949b0579 Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 7 Sep 2020 15:34:58 -0400 Subject: [PATCH 10/10] fix example for executor --- scipio/src/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipio/src/executor.rs b/scipio/src/executor.rs index 3f5f56ce1..a3111b327 100644 --- a/scipio/src/executor.rs +++ b/scipio/src/executor.rs @@ -371,7 +371,7 @@ impl LocalExecutor { /// use scipio::LocalExecutor; /// /// // executor is a single thread, but not bound to any particular CPU. - /// let handle = LocalExecutor::spawn_executor("myname", None, async move { + /// let handle = LocalExecutor::spawn_executor("myname", None, || async move { /// println!("hello"); /// }).unwrap(); ///