-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Nesting Task
scopes
#4343
Nesting Task
scopes
#4343
Changes from all commits
c1183ed
30f5dc0
f92a145
910260b
34075e5
5452926
a876d81
0c3e9da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ use std::{ | |
future::Future, | ||
mem, | ||
pin::Pin, | ||
sync::Arc, | ||
sync::{Arc, Mutex}, | ||
thread::{self, JoinHandle}, | ||
}; | ||
|
||
|
@@ -164,7 +164,7 @@ impl TaskPool { | |
/// This is similar to `rayon::scope` and `crossbeam::scope` | ||
pub fn scope<'scope, F, T>(&self, f: F) -> Vec<T> | ||
where | ||
F: FnOnce(&mut Scope<'scope, T>) + 'scope + Send, | ||
F: FnOnce(&Scope<'scope, T>) + 'scope + Send, | ||
T: Send + 'static, | ||
{ | ||
TaskPool::LOCAL_EXECUTOR.with(|local_executor| { | ||
|
@@ -179,19 +179,20 @@ impl TaskPool { | |
let mut scope = Scope { | ||
executor, | ||
local_executor, | ||
spawned: Vec::new(), | ||
spawned: Arc::new(Mutex::new(Vec::new())), | ||
}; | ||
|
||
f(&mut scope); | ||
|
||
if scope.spawned.is_empty() { | ||
let mut spawned = scope.spawned.lock().unwrap(); | ||
if spawned.is_empty() { | ||
Vec::default() | ||
} else if scope.spawned.len() == 1 { | ||
vec![future::block_on(&mut scope.spawned[0])] | ||
} else if spawned.len() == 1 { | ||
vec![future::block_on(&mut spawned[0])] | ||
} else { | ||
let fut = async move { | ||
let mut results = Vec::with_capacity(scope.spawned.len()); | ||
for task in scope.spawned { | ||
let mut results = Vec::with_capacity(spawned.len()); | ||
for task in spawned.iter_mut() { | ||
results.push(task.await); | ||
} | ||
|
||
|
@@ -265,7 +266,7 @@ impl Default for TaskPool { | |
pub struct Scope<'scope, T> { | ||
executor: &'scope async_executor::Executor<'scope>, | ||
local_executor: &'scope async_executor::LocalExecutor<'scope>, | ||
spawned: Vec<async_executor::Task<T>>, | ||
spawned: Arc<Mutex<Vec<async_executor::Task<T>>>>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replacing this Arc with a reference to a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stack of which thread? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be similar to how the code currently makes a fresh |
||
} | ||
|
||
impl<'scope, T: Send + 'scope> Scope<'scope, T> { | ||
|
@@ -277,9 +278,9 @@ impl<'scope, T: Send + 'scope> Scope<'scope, T> { | |
/// instead. | ||
/// | ||
/// For more information, see [`TaskPool::scope`]. | ||
pub fn spawn<Fut: Future<Output = T> + 'scope + Send>(&mut self, f: Fut) { | ||
pub fn spawn<Fut: Future<Output = T> + 'scope + Send>(&self, f: Fut) { | ||
let task = self.executor.spawn(f); | ||
self.spawned.push(task); | ||
self.spawned.lock().unwrap().push(task); | ||
} | ||
|
||
/// Spawns a scoped future onto the thread-local executor. The scope *must* outlive | ||
|
@@ -288,9 +289,9 @@ impl<'scope, T: Send + 'scope> Scope<'scope, T> { | |
/// [`Scope::spawn`] instead, unless the provided future is not `Send`. | ||
/// | ||
/// For more information, see [`TaskPool::scope`]. | ||
pub fn spawn_local<Fut: Future<Output = T> + 'scope>(&mut self, f: Fut) { | ||
pub fn spawn_local<Fut: Future<Output = T> + 'scope>(&self, f: Fut) { | ||
let task = self.local_executor.spawn(f); | ||
self.spawned.push(task); | ||
self.spawned.lock().unwrap().push(task); | ||
} | ||
} | ||
|
||
|
@@ -334,6 +335,43 @@ mod tests { | |
assert_eq!(count.load(Ordering::Relaxed), 100); | ||
} | ||
|
||
#[test] | ||
fn test_nested_spawn() { | ||
let pool = TaskPool::new(); | ||
|
||
let foo = Box::new(42); | ||
let foo = &*foo; | ||
|
||
let count = Arc::new(AtomicI32::new(0)); | ||
|
||
let outputs = pool.scope(|scope| { | ||
for _ in 0..10 { | ||
let count_clone = count.clone(); | ||
scope.spawn(async move { | ||
for _ in 0..10 { | ||
let count_clone_clone = count_clone.clone(); | ||
scope.spawn(async move { | ||
if *foo != 42 { | ||
panic!("not 42!?!?") | ||
} else { | ||
count_clone_clone.fetch_add(1, Ordering::Relaxed); | ||
*foo | ||
} | ||
}); | ||
} | ||
*foo | ||
}); | ||
} | ||
}); | ||
|
||
for output in &outputs { | ||
assert_eq!(*output, 42); | ||
} | ||
|
||
assert_eq!(outputs.len(), 100); | ||
assert_eq!(count.load(Ordering::Relaxed), 100); | ||
} | ||
|
||
#[test] | ||
fn test_mixed_spawn_local_and_spawn() { | ||
let pool = TaskPool::new(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to avoid the use of Mutexes on the single threaded executor. The primary use case for the single threaded executor is for wasm and wasm doesn't support Atomic wait on the main thread which is what mutexes use when you build with atomics for wasm.
I think without enabling atomics, Mutexes become a no op, so this might work with a default wasm build config today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right
I didn't implement it, but Joy came up with the idea of splitting
scope
into a local variant and non-local variant.Although originally we didn't decide on
results
storage (I was pro-shared), if we split them up we can make a thread safeLocalScope
Signatures would look something like this:
scopes like this:
It's also worth mentioning that there were talks about moving wasm to multithreading although I'm not seeing any progress (nor contributing much :/)