@@ -149,6 +149,85 @@ impl<'a> Executor<'a> {
149
149
pub fn spawn < T : Send + ' a > ( & self , future : impl Future < Output = T > + Send + ' a ) -> Task < T > {
150
150
let mut active = self . state ( ) . active . lock ( ) . unwrap ( ) ;
151
151
152
+ // SAFETY: `T` and the future are `Send`.
153
+ unsafe { self . spawn_inner ( future, & mut active) }
154
+ }
155
+
156
+ /// Spawns many tasks onto the executor.
157
+ ///
158
+ /// As opposed to the [`spawn`] method, this locks the executor's inner task lock once and
159
+ /// spawns all of the tasks in one go. With large amounts of tasks this can improve
160
+ /// contention.
161
+ ///
162
+ /// For very large numbers of tasks the lock is occasionally dropped and re-acquired to
163
+ /// prevent runner thread starvation. It is assumed that the iterator provided does not
164
+ /// block; blocking iterators can lock up the internal mutex and therefore the entire
165
+ /// executor.
166
+ ///
167
+ /// ## Example
168
+ ///
169
+ /// ```
170
+ /// use async_executor::Executor;
171
+ /// use futures_lite::{stream, prelude::*};
172
+ /// use std::future::ready;
173
+ ///
174
+ /// # futures_lite::future::block_on(async {
175
+ /// let mut ex = Executor::new();
176
+ ///
177
+ /// let futures = [
178
+ /// ready(1),
179
+ /// ready(2),
180
+ /// ready(3)
181
+ /// ];
182
+ ///
183
+ /// // Spawn all of the futures onto the executor at once.
184
+ /// let mut tasks = vec![];
185
+ /// ex.spawn_many(futures, &mut tasks);
186
+ ///
187
+ /// // Await all of them.
188
+ /// let results = ex.run(async move {
189
+ /// stream::iter(tasks).then(|x| x).collect::<Vec<_>>().await
190
+ /// }).await;
191
+ /// assert_eq!(results, [1, 2, 3]);
192
+ /// # });
193
+ /// ```
194
+ ///
195
+ /// [`spawn`]: Executor::spawn
196
+ pub fn spawn_many < T : Send + ' a , F : Future < Output = T > + Send + ' a > (
197
+ & self ,
198
+ futures : impl IntoIterator < Item = F > ,
199
+ handles : & mut impl Extend < Task < F :: Output > > ,
200
+ ) {
201
+ let mut active = Some ( self . state ( ) . active . lock ( ) . unwrap ( ) ) ;
202
+
203
+ // Convert the futures into tasks.
204
+ let tasks = futures. into_iter ( ) . enumerate ( ) . map ( move |( i, future) | {
205
+ // SAFETY: `T` and the future are `Send`.
206
+ let task = unsafe { self . spawn_inner ( future, active. as_mut ( ) . unwrap ( ) ) } ;
207
+
208
+ // Yield the lock every once in a while to ease contention.
209
+ if i. wrapping_sub ( 1 ) % 500 == 0 {
210
+ drop ( active. take ( ) ) ;
211
+ active = Some ( self . state ( ) . active . lock ( ) . unwrap ( ) ) ;
212
+ }
213
+
214
+ task
215
+ } ) ;
216
+
217
+ // Push the tasks to the user's collection.
218
+ handles. extend ( tasks) ;
219
+ }
220
+
221
+ /// Spawn a future while holding the inner lock.
222
+ ///
223
+ /// # Safety
224
+ ///
225
+ /// If this is an `Executor`, `F` and `T` must be `Send`.
226
+ unsafe fn spawn_inner < T : ' a > (
227
+ & self ,
228
+ future : impl Future < Output = T > + ' a ,
229
+ active : & mut Slab < Waker > ,
230
+ ) -> Task < T > {
152
231
// Remove the task from the set of active tasks when the future finishes.
153
232
let entry = active. vacant_entry ( ) ;
154
233
let index = entry. key ( ) ;
@@ -159,11 +238,30 @@ impl<'a> Executor<'a> {
159
238
} ;
160
239
161
240
// Create the task and register it in the set of active tasks.
162
- let ( runnable, task) = unsafe {
163
- Builder :: new ( )
164
- . propagate_panic ( true )
165
- . spawn_unchecked ( |( ) | future, self . schedule ( ) )
166
- } ;
241
+ //
242
+ // SAFETY:
243
+ //
244
+ // If `future` is not `Send`, this must be a `LocalExecutor` as per this
245
+ // function's unsafe precondition. Since `LocalExecutor` is `!Sync`,
246
+ // `try_tick`, `tick` and `run` can only be called from the origin
247
+ // thread of the `LocalExecutor`. Similarly, `spawn` can only be called
248
+ // from the origin thread, ensuring that `future` and the executor share
249
+ // the same origin thread. The `Runnable` can be scheduled from other
250
+ // threads, but because of the above `Runnable` can only be called or
251
+ // dropped on the origin thread.
252
+ //
253
+ // `future` is not `'static`, but we make sure that the `Runnable` does
254
+ // not outlive `'a`. When the executor is dropped, the `active` field is
255
+ // drained and all of the `Waker`s are woken. Then, the queue inside of
256
+ // the `Executor` is drained of all of its runnables. This ensures that
257
+ // runnables are dropped and this precondition is satisfied.
258
+ //
259
+ // `self.schedule()` is `Send`, `Sync` and `'static`, as checked below.
260
+ // Therefore we do not need to worry about what is done with the
261
+ // `Waker`.
262
+ let ( runnable, task) = Builder :: new ( )
263
+ . propagate_panic ( true )
264
+ . spawn_unchecked ( |( ) | future, self . schedule ( ) ) ;
167
265
entry. insert ( runnable. waker ( ) ) ;
168
266
169
267
runnable. schedule ( ) ;
@@ -292,7 +390,7 @@ impl<'a> Executor<'a> {
292
390
impl Drop for Executor < ' _ > {
293
391
fn drop ( & mut self ) {
294
392
if let Some ( state) = self . state . get ( ) {
295
- let mut active = state. active . lock ( ) . unwrap ( ) ;
393
+ let mut active = state. active . lock ( ) . unwrap_or_else ( |e| e . into_inner ( ) ) ;
296
394
for w in active. drain ( ) {
297
395
w. wake ( ) ;
298
396
}
@@ -397,25 +495,70 @@ impl<'a> LocalExecutor<'a> {
397
495
pub fn spawn < T : ' a > ( & self , future : impl Future < Output = T > + ' a ) -> Task < T > {
398
496
let mut active = self . inner ( ) . state ( ) . active . lock ( ) . unwrap ( ) ;
399
497
400
- // Remove the task from the set of active tasks when the future finishes.
401
- let entry = active. vacant_entry ( ) ;
402
- let index = entry. key ( ) ;
403
- let state = self . inner ( ) . state ( ) . clone ( ) ;
404
- let future = async move {
405
- let _guard = CallOnDrop ( move || drop ( state. active . lock ( ) . unwrap ( ) . try_remove ( index) ) ) ;
406
- future. await
407
- } ;
498
+ // SAFETY: This executor is not thread safe, so the future and its result
499
+ // cannot be sent to another thread.
500
+ unsafe { self . inner ( ) . spawn_inner ( future, & mut active) }
501
+ }
408
502
409
- // Create the task and register it in the set of active tasks.
410
- let ( runnable, task) = unsafe {
411
- Builder :: new ( )
412
- . propagate_panic ( true )
413
- . spawn_unchecked ( |( ) | future, self . schedule ( ) )
414
- } ;
415
- entry. insert ( runnable. waker ( ) ) ;
503
+ /// Spawns many tasks onto the executor.
504
+ ///
505
+ /// As opposed to the [`spawn`] method, this locks the executor's inner task lock once and
506
+ /// spawns all of the tasks in one go. With large amounts of tasks this can improve
507
+ /// contention.
508
+ ///
509
+ /// It is assumed that the iterator provided does not block; blocking iterators can lock up
510
+ /// the internal mutex and therefore the entire executor. Unlike [`Executor::spawn`], the
511
+ /// mutex is not released, as there are no other threads that can poll this executor.
512
+ ///
513
+ /// ## Example
514
+ ///
515
+ /// ```
516
+ /// use async_executor::LocalExecutor;
517
+ /// use futures_lite::{stream, prelude::*};
518
+ /// use std::future::ready;
519
+ ///
520
+ /// # futures_lite::future::block_on(async {
521
+ /// let mut ex = LocalExecutor::new();
522
+ ///
523
+ /// let futures = [
524
+ /// ready(1),
525
+ /// ready(2),
526
+ /// ready(3)
527
+ /// ];
528
+ ///
529
+ /// // Spawn all of the futures onto the executor at once.
530
+ /// let mut tasks = vec![];
531
+ /// ex.spawn_many(futures, &mut tasks);
532
+ ///
533
+ /// // Await all of them.
534
+ /// let results = ex.run(async move {
535
+ /// stream::iter(tasks).then(|x| x).collect::<Vec<_>>().await
536
+ /// }).await;
537
+ /// assert_eq!(results, [1, 2, 3]);
538
+ /// # });
539
+ /// ```
540
+ ///
541
+ /// [`spawn`]: LocalExecutor::spawn
542
+ /// [`Executor::spawn_many`]: Executor::spawn_many
543
+ pub fn spawn_many < T : Send + ' a , F : Future < Output = T > + Send + ' a > (
544
+ & self ,
545
+ futures : impl IntoIterator < Item = F > ,
546
+ handles : & mut impl Extend < Task < F :: Output > > ,
547
+ ) {
548
+ let mut active = self . inner ( ) . state ( ) . active . lock ( ) . unwrap ( ) ;
416
549
417
- runnable. schedule ( ) ;
418
- task
550
+ // Convert all of the futures to tasks.
551
+ let tasks = futures. into_iter ( ) . map ( |future| {
552
+ // SAFETY: This executor is not thread safe, so the future and its result
553
+ // cannot be sent to another thread.
554
+ unsafe { self . inner ( ) . spawn_inner ( future, & mut active) }
555
+
556
+ // As only one thread can spawn or poll tasks at a time, there is no need
557
+ // to release lock contention here.
558
+ } ) ;
559
+
560
+ // Push them to the user's collection.
561
+ handles. extend ( tasks) ;
419
562
}
420
563
421
564
/// Attempts to run a task if at least one is scheduled.
@@ -481,16 +624,6 @@ impl<'a> LocalExecutor<'a> {
481
624
self . inner ( ) . run ( future) . await
482
625
}
483
626
484
- /// Returns a function that schedules a runnable task when it gets woken up.
485
- fn schedule ( & self ) -> impl Fn ( Runnable ) + Send + Sync + ' static {
486
- let state = self . inner ( ) . state ( ) . clone ( ) ;
487
-
488
- move |runnable| {
489
- state. queue . push ( runnable) . unwrap ( ) ;
490
- state. notify ( ) ;
491
- }
492
- }
493
-
494
627
/// Returns a reference to the inner executor.
495
628
fn inner ( & self ) -> & Executor < ' a > {
496
629
& self . inner
@@ -953,6 +1086,7 @@ fn _ensure_send_and_sync() {
953
1086
954
1087
fn is_send < T : Send > ( _: T ) { }
955
1088
fn is_sync < T : Sync > ( _: T ) { }
1089
+ fn is_static < T : ' static > ( _: T ) { }
956
1090
957
1091
is_send :: < Executor < ' _ > > ( Executor :: new ( ) ) ;
958
1092
is_sync :: < Executor < ' _ > > ( Executor :: new ( ) ) ;
@@ -962,6 +1096,9 @@ fn _ensure_send_and_sync() {
962
1096
is_sync ( ex. run ( pending :: < ( ) > ( ) ) ) ;
963
1097
is_send ( ex. tick ( ) ) ;
964
1098
is_sync ( ex. tick ( ) ) ;
1099
+ is_send ( ex. schedule ( ) ) ;
1100
+ is_sync ( ex. schedule ( ) ) ;
1101
+ is_static ( ex. schedule ( ) ) ;
965
1102
966
1103
/// ```compile_fail
967
1104
/// use async_executor::LocalExecutor;
0 commit comments