Skip to main content

tokio/runtime/task/
list.rs

1//! This module has containers for storing the tasks spawned on a scheduler. The
2//! `OwnedTasks` container is thread-safe but can only store tasks that
3//! implement Send. The `LocalOwnedTasks` container is not thread safe, but can
4//! store non-Send tasks.
5//!
6//! The collections can be closed to prevent adding new tasks during shutdown of
7//! the scheduler with the collection.
8
9use crate::future::Future;
10use crate::loom::cell::UnsafeCell;
11use crate::runtime::task::{JoinHandle, LocalNotified, Notified, Schedule, SpawnLocation, Task};
12use crate::util::linked_list::LinkedList;
13use crate::util::sharded_list::ShardedList;
14
15use crate::loom::sync::atomic::{AtomicBool, Ordering};
16use std::marker::PhantomData;
17use std::num::NonZeroU64;
18
19// The id from the module below is used to verify whether a given task is stored
20// in this OwnedTasks, or some other task. The counter starts at one so we can
21// use `None` for tasks not owned by any list.
22//
23// The safety checks in this file can technically be violated if the counter is
24// overflown, but the checks are not supposed to ever fail unless there is a
25// bug in Tokio, so we accept that certain bugs would not be caught if the two
26// mixed up runtimes happen to have the same id.
27
28cfg_has_atomic_u64! {
29    use std::sync::atomic::AtomicU64;
30
31    static NEXT_OWNED_TASKS_ID: AtomicU64 = AtomicU64::new(1);
32
33    fn get_next_id() -> NonZeroU64 {
34        loop {
35            let id = NEXT_OWNED_TASKS_ID.fetch_add(1, Ordering::Relaxed);
36            if let Some(id) = NonZeroU64::new(id) {
37                return id;
38            }
39        }
40    }
41}
42
43cfg_not_has_atomic_u64! {
44    use std::sync::atomic::AtomicU32;
45
46    static NEXT_OWNED_TASKS_ID: AtomicU32 = AtomicU32::new(1);
47
48    fn get_next_id() -> NonZeroU64 {
49        loop {
50            let id = NEXT_OWNED_TASKS_ID.fetch_add(1, Ordering::Relaxed);
51            if let Some(id) = NonZeroU64::new(u64::from(id)) {
52                return id;
53            }
54        }
55    }
56}
57
58pub(crate) struct OwnedTasks<S: 'static> {
59    list: ShardedList<Task<S>>,
60    pub(crate) id: NonZeroU64,
61    closed: AtomicBool,
62}
63
64pub(crate) struct LocalOwnedTasks<S: 'static> {
65    inner: UnsafeCell<OwnedTasksInner<S>>,
66    pub(crate) id: NonZeroU64,
67    _not_send_or_sync: PhantomData<*const ()>,
68}
69
70struct OwnedTasksInner<S: 'static> {
71    list: LinkedList<Task<S>>,
72    closed: bool,
73}
74
75impl<S: 'static> OwnedTasks<S> {
76    pub(crate) fn new(num_cores: usize) -> Self {
77        let shard_size = Self::gen_shared_list_size(num_cores);
78        Self {
79            list: ShardedList::new(shard_size),
80            closed: AtomicBool::new(false),
81            id: get_next_id(),
82        }
83    }
84
85    /// Binds the provided task to this `OwnedTasks` instance. This fails if the
86    /// `OwnedTasks` has been closed.
87    pub(crate) fn bind<T>(
88        &self,
89        task: T,
90        scheduler: S,
91        id: super::Id,
92        spawned_at: SpawnLocation,
93    ) -> (JoinHandle<T::Output>, Option<Notified<S>>)
94    where
95        S: Schedule,
96        T: Future + Send + 'static,
97        T::Output: Send + 'static,
98    {
99        let (task, notified, join) = super::new_task(task, scheduler, id, spawned_at);
100        let notified = unsafe { self.bind_inner(task, notified) };
101        (join, notified)
102    }
103
104    /// Bind a task that isn't safe to transfer across thread boundaries.
105    ///
106    /// # Safety
107    ///
108    /// Only use this in `LocalRuntime` where the task cannot move
109    pub(crate) unsafe fn bind_local<T>(
110        &self,
111        task: T,
112        scheduler: S,
113        id: super::Id,
114        spawned_at: SpawnLocation,
115    ) -> (JoinHandle<T::Output>, Option<Notified<S>>)
116    where
117        S: Schedule,
118        T: Future + 'static,
119        T::Output: 'static,
120    {
121        let (task, notified, join) = super::new_task(task, scheduler, id, spawned_at);
122        let notified = unsafe { self.bind_inner(task, notified) };
123        (join, notified)
124    }
125
126    /// The part of `bind` that's the same for every type of future.
127    unsafe fn bind_inner(&self, task: Task<S>, notified: Notified<S>) -> Option<Notified<S>>
128    where
129        S: Schedule,
130    {
131        unsafe {
132            // safety: We just created the task, so we have exclusive access
133            // to the field.
134            task.header().set_owner_id(self.id);
135        }
136
137        let shard = self.list.lock_shard(&task);
138        // Check the closed flag in the lock for ensuring all that tasks
139        // will shut down after the OwnedTasks has been closed.
140        if self.closed.load(Ordering::Acquire) {
141            drop(shard);
142            task.shutdown();
143            return None;
144        }
145        shard.push(task);
146        Some(notified)
147    }
148
149    /// Asserts that the given task is owned by this `OwnedTasks` and convert it to
150    /// a `LocalNotified`, giving the thread permission to poll this task.
151    #[inline]
152    pub(crate) fn assert_owner(&self, task: Notified<S>) -> LocalNotified<S> {
153        debug_assert_eq!(task.header().get_owner_id(), Some(self.id));
154        // safety: All tasks bound to this OwnedTasks are Send, so it is safe
155        // to poll it on this thread no matter what thread we are on.
156        LocalNotified {
157            task: task.0,
158            _not_send: PhantomData,
159        }
160    }
161
162    /// Shuts down all tasks in the collection. This call also closes the
163    /// collection, preventing new items from being added.
164    ///
165    /// The parameter start determines which shard this method will start at.
166    /// Using different values for each worker thread reduces contention.
167    pub(crate) fn close_and_shutdown_all(&self, start: usize)
168    where
169        S: Schedule,
170    {
171        self.closed.store(true, Ordering::Release);
172        for i in start..self.get_shard_size() + start {
173            loop {
174                let task = self.list.pop_back(i);
175                match task {
176                    Some(task) => {
177                        task.shutdown();
178                    }
179                    None => break,
180                }
181            }
182        }
183    }
184
185    #[inline]
186    pub(crate) fn get_shard_size(&self) -> usize {
187        self.list.shard_size()
188    }
189
190    pub(crate) fn num_alive_tasks(&self) -> usize {
191        self.list.len()
192    }
193
194    cfg_unstable_metrics! {
195        cfg_64bit_metrics! {
196            pub(crate) fn spawned_tasks_count(&self) -> u64 {
197                self.list.added()
198            }
199        }
200    }
201
202    pub(crate) fn remove(&self, task: &Task<S>) -> Option<Task<S>> {
203        // If the task's owner ID is `None` then it is not part of any list and
204        // doesn't need removing.
205        let task_id = task.header().get_owner_id()?;
206
207        assert_eq!(task_id, self.id);
208
209        // safety: We just checked that the provided task is not in some other
210        // linked list.
211        unsafe { self.list.remove(task.header_ptr()) }
212    }
213
214    pub(crate) fn is_empty(&self) -> bool {
215        self.list.is_empty()
216    }
217
218    /// Generates the size of the sharded list based on the number of worker threads.
219    ///
220    /// The sharded lock design can effectively alleviate
221    /// lock contention performance problems caused by high concurrency.
222    ///
223    /// However, as the number of shards increases, the memory continuity between
224    /// nodes in the intrusive linked list will diminish. Furthermore,
225    /// the construction time of the sharded list will also increase with a higher number of shards.
226    ///
227    /// Due to the above reasons, we set a maximum value for the shared list size,
228    /// denoted as `MAX_SHARED_LIST_SIZE`.
229    fn gen_shared_list_size(num_cores: usize) -> usize {
230        const MAX_SHARED_LIST_SIZE: usize = 1 << 16;
231        usize::min(MAX_SHARED_LIST_SIZE, num_cores.next_power_of_two() * 4)
232    }
233}
234
235cfg_taskdump! {
236    impl<S: 'static> OwnedTasks<S> {
237        /// Locks the tasks, and calls `f` on an iterator over them.
238        pub(crate) fn for_each<F>(&self, f: F)
239        where
240            F: FnMut(&Task<S>),
241        {
242            self.list.for_each(f);
243        }
244    }
245}
246
247impl<S: 'static> LocalOwnedTasks<S> {
248    pub(crate) fn new() -> Self {
249        Self {
250            inner: UnsafeCell::new(OwnedTasksInner {
251                list: LinkedList::new(),
252                closed: false,
253            }),
254            id: get_next_id(),
255            _not_send_or_sync: PhantomData,
256        }
257    }
258
259    pub(crate) fn bind<T>(
260        &self,
261        task: T,
262        scheduler: S,
263        id: super::Id,
264        spawned_at: SpawnLocation,
265    ) -> (JoinHandle<T::Output>, Option<Notified<S>>)
266    where
267        S: Schedule,
268        T: Future + 'static,
269        T::Output: 'static,
270    {
271        let (task, notified, join) = super::new_task(task, scheduler, id, spawned_at);
272
273        unsafe {
274            // safety: We just created the task, so we have exclusive access
275            // to the field.
276            task.header().set_owner_id(self.id);
277        }
278
279        if self.is_closed() {
280            drop(notified);
281            task.shutdown();
282            (join, None)
283        } else {
284            self.with_inner(|inner| {
285                inner.list.push_front(task);
286            });
287            (join, Some(notified))
288        }
289    }
290
291    /// Shuts down all tasks in the collection. This call also closes the
292    /// collection, preventing new items from being added.
293    pub(crate) fn close_and_shutdown_all(&self)
294    where
295        S: Schedule,
296    {
297        self.with_inner(|inner| inner.closed = true);
298
299        while let Some(task) = self.with_inner(|inner| inner.list.pop_back()) {
300            task.shutdown();
301        }
302    }
303
304    pub(crate) fn remove(&self, task: &Task<S>) -> Option<Task<S>> {
305        // If the task's owner ID is `None` then it is not part of any list and
306        // doesn't need removing.
307        let task_id = task.header().get_owner_id()?;
308
309        assert_eq!(task_id, self.id);
310
311        self.with_inner(|inner|
312            // safety: We just checked that the provided task is not in some
313            // other linked list.
314            unsafe { inner.list.remove(task.header_ptr()) })
315    }
316
317    /// Asserts that the given task is owned by this `LocalOwnedTasks` and convert
318    /// it to a `LocalNotified`, giving the thread permission to poll this task.
319    #[inline]
320    pub(crate) fn assert_owner(&self, task: Notified<S>) -> LocalNotified<S> {
321        assert_eq!(task.header().get_owner_id(), Some(self.id));
322
323        // safety: The task was bound to this LocalOwnedTasks, and the
324        // LocalOwnedTasks is not Send or Sync, so we are on the right thread
325        // for polling this task.
326        LocalNotified {
327            task: task.0,
328            _not_send: PhantomData,
329        }
330    }
331
332    #[inline]
333    fn with_inner<F, T>(&self, f: F) -> T
334    where
335        F: FnOnce(&mut OwnedTasksInner<S>) -> T,
336    {
337        // safety: This type is not Sync, so concurrent calls of this method
338        // can't happen.  Furthermore, all uses of this method in this file make
339        // sure that they don't call `with_inner` recursively.
340        self.inner.with_mut(|ptr| unsafe { f(&mut *ptr) })
341    }
342
343    pub(crate) fn is_closed(&self) -> bool {
344        self.with_inner(|inner| inner.closed)
345    }
346
347    pub(crate) fn is_empty(&self) -> bool {
348        self.with_inner(|inner| inner.list.is_empty())
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    // This test may run in parallel with other tests, so we only test that ids
357    // come in increasing order.
358    #[test]
359    fn test_id_not_broken() {
360        let mut last_id = get_next_id();
361
362        for _ in 0..1000 {
363            let next_id = get_next_id();
364            assert!(last_id < next_id);
365            last_id = next_id;
366        }
367    }
368}