|
1 | 1 | //! Task abstraction for building executors.
|
2 | 2 | //!
|
3 |
| -//! # What is an executor? |
| 3 | +//! To spawn a future onto an executor, we first need to allocate it on the heap and keep some |
| 4 | +//! state alongside it. The state indicates whether the future is ready for polling, waiting to be |
| 5 | +//! woken up, or completed. Such a future is called a *task*. |
4 | 6 | //!
|
5 |
| -//! An async block creates a future and an async function returns one. But futures don't do |
6 |
| -//! anything unless they are awaited inside other async blocks or async functions. So the question |
7 |
| -//! arises: who or what awaits the main future that awaits others? |
| 7 | +//! This crate helps with task allocation and polling its future to completion. |
8 | 8 | //!
|
9 |
| -//! One solution is to call [`block_on()`] on the main future, which will block |
10 |
| -//! the current thread and keep polling the future until it completes. But sometimes we don't want |
11 |
| -//! to block the current thread and would prefer to *spawn* the future to let a background thread |
12 |
| -//! block on it instead. |
| 9 | +//! # Spawning |
13 | 10 | //!
|
14 |
| -//! This is where executors step in - they create a number of threads (typically equal to the |
15 |
| -//! number of CPU cores on the system) that are dedicated to polling spawned futures. Each executor |
16 |
| -//! thread keeps polling spawned futures in a loop and only blocks when all spawned futures are |
17 |
| -//! either sleeping or running. |
| 11 | +//! All executors have some kind of queue that holds runnable tasks: |
18 | 12 | //!
|
19 |
| -//! # What is a task? |
20 |
| -//! |
21 |
| -//! In order to spawn a future on an executor, one needs to allocate the future on the heap and |
22 |
| -//! keep some state alongside it, like whether the future is ready for polling, waiting to be woken |
23 |
| -//! up, or completed. This allocation is usually called a *task*. |
24 |
| -//! |
25 |
| -//! The executor then runs the spawned task by polling its future. If the future is pending on a |
26 |
| -//! resource, a [`Waker`] associated with the task will be registered somewhere so that the task |
27 |
| -//! can be woken up and run again at a later time. |
28 |
| -//! |
29 |
| -//! For example, if the future wants to read something from a TCP socket that is not ready yet, the |
30 |
| -//! networking system will clone the task's waker and wake it up once the socket becomes ready. |
31 |
| -//! |
32 |
| -//! # Task construction |
| 13 | +//! ``` |
| 14 | +//! # #![feature(async_await)] |
| 15 | +//! # |
| 16 | +//! let (sender, receiver) = crossbeam::channel::unbounded(); |
| 17 | +//! # |
| 18 | +//! # // A future that will get spawned. |
| 19 | +//! # let future = async { 1 + 2 }; |
| 20 | +//! # |
| 21 | +//! # // A function that schedules the task when it gets woken up. |
| 22 | +//! # let schedule = move |task| sender.send(task).unwrap(); |
| 23 | +//! # |
| 24 | +//! # // Construct a task. |
| 25 | +//! # let (task, handle) = async_task::spawn(future, schedule, ()); |
| 26 | +//! ``` |
33 | 27 | //!
|
34 |
| -//! A task is constructed with [`Task::create()`]: |
| 28 | +//! A task is constructed using the [`spawn`] function: |
35 | 29 | //!
|
36 | 30 | //! ```
|
37 | 31 | //! # #![feature(async_await)]
|
| 32 | +//! # |
| 33 | +//! # let (sender, receiver) = crossbeam::channel::unbounded(); |
| 34 | +//! # |
| 35 | +//! // A future that will be spawned. |
38 | 36 | //! let future = async { 1 + 2 };
|
39 |
| -//! let schedule = |task| unimplemented!(); |
40 | 37 | //!
|
41 |
| -//! let (task, handle) = async_task::spawn(future, schedule, ()); |
42 |
| -//! ``` |
| 38 | +//! // A function that schedules the task when it gets woken up. |
| 39 | +//! let schedule = move |task| sender.send(task).unwrap(); |
43 | 40 | //!
|
44 |
| -//! The first argument to the constructor, `()` in this example, is an arbitrary piece of data |
45 |
| -//! called a *tag*. This can be a task identifier, a task name, task-local storage, or something |
46 |
| -//! of similar nature. |
| 41 | +//! // Construct a task. |
| 42 | +//! let (task, handle) = async_task::spawn(future, schedule, ()); |
47 | 43 | //!
|
48 |
| -//! The second argument is the future that gets polled when the task is run. |
| 44 | +//! // Push the task into the queue by invoking its schedule function. |
| 45 | +//! task.schedule(); |
| 46 | +//! ``` |
49 | 47 | //!
|
50 |
| -//! The third argument is the schedule function, which is called every time when the task gets |
51 |
| -//! woken up. This function should push the received task into some kind of queue of runnable |
52 |
| -//! tasks. |
| 48 | +//! The last argument to the [`spawn`] function is a *tag*, an arbitrary piece of data associated |
| 49 | +//! with the task. In most executors, this is typically a task identifier or task-local storage. |
53 | 50 | //!
|
54 |
| -//! The constructor returns a runnable [`Task`] and a [`JoinHandle`] that can await the result of |
55 |
| -//! the future. |
| 51 | +//! The function returns a runnable [`Task`] and a [`JoinHandle`] that can await the result. |
56 | 52 | //!
|
57 |
| -//! # Task scheduling |
| 53 | +//! # Execution |
58 | 54 | //!
|
59 |
| -//! TODO |
| 55 | +//! Task executors have some kind of main loop that drives tasks to completion. That means taking |
| 56 | +//! runnable tasks out of the queue and running each one in order: |
60 | 57 | //!
|
61 |
| -//! # Join handles |
| 58 | +//! ```no_run |
| 59 | +//! # #![feature(async_await)] |
| 60 | +//! # |
| 61 | +//! # let (sender, receiver) = crossbeam::channel::unbounded(); |
| 62 | +//! # |
| 63 | +//! # // A future that will get spawned. |
| 64 | +//! # let future = async { 1 + 2 }; |
| 65 | +//! # |
| 66 | +//! # // A function that schedules the task when it gets woken up. |
| 67 | +//! # let schedule = move |task| sender.send(task).unwrap(); |
| 68 | +//! # |
| 69 | +//! # // Construct a task. |
| 70 | +//! # let (task, handle) = async_task::spawn(future, schedule, ()); |
| 71 | +//! # |
| 72 | +//! # // Push the task into the queue by invoking its schedule function. |
| 73 | +//! # task.schedule(); |
| 74 | +//! # |
| 75 | +//! for task in receiver { |
| 76 | +//! task.run(); |
| 77 | +//! } |
| 78 | +//! ``` |
62 | 79 | //!
|
63 |
| -//! TODO |
| 80 | +//! When a task is run, its future gets polled. If polling does not complete the task, that means |
| 81 | +//! it's waiting for another future and needs to go to sleep. When woken up, its schedule function |
| 82 | +//! will be invoked, pushing it back into the queue so that it can be run again. |
64 | 83 | //!
|
65 | 84 | //! # Cancellation
|
66 | 85 | //!
|
67 |
| -//! TODO |
| 86 | +//! Both [`Task`] and [`JoinHandle`] have a method that cancels the task. When cancelled, the |
| 87 | +//! task's future will not be polled again and will get dropped instead. |
| 88 | +//! |
| 89 | +//! If cancelled by the [`Task`] instance, the task is destroyed immediately. If cancelled by the |
| 90 | +//! [`JoinHandle`] instance, it will be scheduled one more time and the next attempt to run it will |
| 91 | +//! simply destroy it. |
68 | 92 | //!
|
69 | 93 | //! # Performance
|
70 | 94 | //!
|
71 |
| -//! TODO: explain single allocation, etc. |
| 95 | +//! Task construction incurs a single allocation only that holds its state, the schedule function, |
| 96 | +//! and the future or the result of the future if completed. |
72 | 97 | //!
|
73 |
| -//! Task [construction] incurs a single allocation only. The [`Task`] can then be run and its |
74 |
| -//! result awaited through the [`JoinHandle`]. When woken, the task gets automatically rescheduled. |
75 |
| -//! It's also possible to cancel the task so that it stops running and can't be awaited anymore. |
| 98 | +//! The layout of a task is equivalent to 4 words followed by the schedule function, and then by a |
| 99 | +//! union of the future and its output. |
76 | 100 | //!
|
77 |
| -//! [construction]: struct.Task.html#method.create |
78 |
| -//! [`JoinHandle`]: struct.JoinHandle.html |
| 101 | +//! [`spawn`]: fn.spawn.html |
79 | 102 | //! [`Task`]: struct.Task.html
|
80 |
| -//! [`Future`]: https://doc.rust-lang.org/nightly/std/future/trait.Future.html |
81 |
| -//! [`Waker`]: https://doc.rust-lang.org/nightly/std/task/struct.Waker.html |
82 |
| -//! [`block_on()`]: https://docs.rs/futures-preview/*/futures/executor/fn.block_on.html |
83 |
| -//! |
84 |
| -//! # Examples |
85 |
| -//! |
86 |
| -//! A simple single-threaded executor: |
87 |
| -//! |
88 |
| -//! ``` |
89 |
| -//! # #![feature(async_await)] |
90 |
| -//! use std::future::Future; |
91 |
| -//! use std::panic::catch_unwind; |
92 |
| -//! use std::thread; |
93 |
| -//! |
94 |
| -//! use async_task::{JoinHandle, Task}; |
95 |
| -//! use crossbeam::channel::{unbounded, Sender}; |
96 |
| -//! use futures::executor; |
97 |
| -//! use lazy_static::lazy_static; |
98 |
| -//! |
99 |
| -//! /// Spawns a future on the executor. |
100 |
| -//! fn spawn<F, R>(future: F) -> JoinHandle<R, ()> |
101 |
| -//! where |
102 |
| -//! F: Future<Output = R> + Send + 'static, |
103 |
| -//! R: Send + 'static, |
104 |
| -//! { |
105 |
| -//! lazy_static! { |
106 |
| -//! // A channel that holds scheduled tasks. |
107 |
| -//! static ref QUEUE: Sender<Task<()>> = { |
108 |
| -//! let (sender, receiver) = unbounded::<Task<()>>(); |
109 |
| -//! |
110 |
| -//! // Start the executor thread. |
111 |
| -//! thread::spawn(|| { |
112 |
| -//! for task in receiver { |
113 |
| -//! // Ignore panics for simplicity. |
114 |
| -//! let _ignore_panic = catch_unwind(|| task.run()); |
115 |
| -//! } |
116 |
| -//! }); |
117 |
| -//! |
118 |
| -//! sender |
119 |
| -//! }; |
120 |
| -//! } |
121 |
| -//! |
122 |
| -//! // Create a task that is scheduled by sending itself into the channel. |
123 |
| -//! let schedule = |t| QUEUE.send(t).unwrap(); |
124 |
| -//! let (task, handle) = async_task::spawn(future, schedule, ()); |
125 |
| -//! |
126 |
| -//! // Schedule the task by sending it into the channel. |
127 |
| -//! task.schedule(); |
128 |
| -//! |
129 |
| -//! handle |
130 |
| -//! } |
131 |
| -//! |
132 |
| -//! // Spawn a future and await its result. |
133 |
| -//! let handle = spawn(async { |
134 |
| -//! println!("Hello, world!"); |
135 |
| -//! }); |
136 |
| -//! executor::block_on(handle); |
137 |
| -//! ``` |
| 103 | +//! [`JoinHandle`]: struct.JoinHandle.html |
138 | 104 |
|
139 | 105 | #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
|
| 106 | +#![doc(test(attr(deny(rust_2018_idioms, warnings))))] |
| 107 | +#![doc(test(attr(allow(unused_extern_crates, unused_variables))))] |
140 | 108 |
|
141 | 109 | mod header;
|
142 | 110 | mod join_handle;
|
|
0 commit comments