Skip to main content

tokio/runtime/scheduler/inject/
rt_multi_thread.rs

1use super::{Inject, Pop};
2
3use crate::runtime::task;
4
5use std::sync::atomic::Ordering::Release;
6
7/// The multi-thread scheduler's inject queue. Each variant owns its queue and
8/// lock topology.
9pub(crate) enum InjectQueue<T: 'static> {
10    /// A single queue behind a single mutex.
11    Locked(Inject<T>),
12}
13
14impl<T: 'static> InjectQueue<T> {
15    pub(crate) fn new() -> InjectQueue<T> {
16        InjectQueue::Locked(Inject::new())
17    }
18
19    pub(crate) fn is_empty(&self) -> bool {
20        match self {
21            InjectQueue::Locked(q) => q.is_empty(),
22        }
23    }
24
25    pub(crate) fn len(&self) -> usize {
26        match self {
27            InjectQueue::Locked(q) => q.len(),
28        }
29    }
30
31    pub(crate) fn is_closed(&self) -> bool {
32        match self {
33            InjectQueue::Locked(q) => q.is_closed(),
34        }
35    }
36
37    /// Closes the queue, returns `true` if the queue was open when the
38    /// transition was made.
39    pub(crate) fn close(&self) -> bool {
40        match self {
41            InjectQueue::Locked(q) => q.close(),
42        }
43    }
44
45    /// Pushes a value into the queue.
46    ///
47    /// This does nothing if the queue is closed.
48    pub(crate) fn push(&self, task: task::Notified<T>) {
49        match self {
50            InjectQueue::Locked(q) => q.push(task),
51        }
52    }
53
54    pub(crate) fn pop(&self) -> Option<task::Notified<T>> {
55        match self {
56            InjectQueue::Locked(q) => q.pop(),
57        }
58    }
59
60    /// Pushes several values into the queue.
61    ///
62    /// This does nothing if the queue is closed.
63    pub(crate) fn push_batch<I>(&self, iter: I)
64    where
65        I: Iterator<Item = task::Notified<T>>,
66    {
67        match self {
68            InjectQueue::Locked(q) => q.push_batch(iter),
69        }
70    }
71
72    /// Pops up to `n` values from the queue, passing an iterator over them to
73    /// `f`. Any values `f` does not consume are removed from the queue and
74    /// dropped.
75    pub(crate) fn pop_n<R>(&self, n: usize, f: impl FnOnce(Pop<'_, T>) -> R) -> R {
76        match self {
77            InjectQueue::Locked(q) => q.pop_n(n, f),
78        }
79    }
80
81    /// Pops every task from the queue into `dst`, atomically with respect to
82    /// concurrent pushes.
83    #[cfg(all(tokio_unstable, feature = "taskdump"))]
84    pub(crate) fn drain_into(&self, dst: &mut Vec<task::Notified<T>>) {
85        match self {
86            InjectQueue::Locked(q) => q.drain_into(dst),
87        }
88    }
89}
90
91impl<T: 'static> Inject<T> {
92    pub(crate) fn is_empty(&self) -> bool {
93        self.shared.is_empty()
94    }
95
96    /// Pushes several values into the queue.
97    #[inline]
98    pub(crate) fn push_batch<I>(&self, mut iter: I)
99    where
100        I: Iterator<Item = task::Notified<T>>,
101    {
102        let first = match iter.next() {
103            Some(first) => first.into_raw(),
104            None => return,
105        };
106
107        // Link up all the tasks.
108        let mut prev = first;
109        let mut counter = 1;
110
111        // We are going to be called with an `std::iter::Chain`, and that
112        // iterator overrides `for_each` to something that is easier for the
113        // compiler to optimize than a loop.
114        iter.for_each(|next| {
115            let next = next.into_raw();
116
117            // safety: Holding the Notified for a task guarantees exclusive
118            // access to the `queue_next` field.
119            unsafe { prev.set_queue_next(Some(next)) };
120            prev = next;
121            counter += 1;
122        });
123
124        // Now that the tasks are linked together, insert them into the
125        // linked list.
126        //
127        // safety: the batch was linked just above from `Notified`s this
128        // function took ownership of, satisfying both obligations.
129        unsafe { self.push_batch_inner(first, prev, counter) };
130    }
131
132    /// Inserts several tasks that have been linked together into the queue.
133    ///
134    /// The provided head and tail may be the same task. In this case, a
135    /// single task is inserted.
136    ///
137    /// # Safety
138    ///
139    /// The caller must own the `Notified` for each of the `num` tasks, and
140    /// the tasks must be linked from `batch_head` to `batch_tail` through
141    /// their `queue_next` fields, with `batch_tail`'s `queue_next` unset.
142    #[inline]
143    unsafe fn push_batch_inner(
144        &self,
145        batch_head: task::RawTask,
146        batch_tail: task::RawTask,
147        num: usize,
148    ) {
149        debug_assert!(unsafe { batch_tail.get_queue_next().is_none() });
150
151        let mut synced = self.synced.lock();
152
153        if synced.is_closed {
154            // Drop the lock before dropping the tasks: dropping a task can
155            // run arbitrary user `Drop` code, which may reentrantly acquire
156            // this lock by scheduling a task.
157            drop(synced);
158
159            let mut curr = Some(batch_head);
160
161            while let Some(task) = curr {
162                // safety: per this function's contract, the caller owns each
163                // task's `Notified` and linked the batch through `queue_next`;
164                // reconstituting the `Notified` here takes that ownership.
165                curr = unsafe { task.get_queue_next() };
166
167                let _ = unsafe { task::Notified::<T>::from_raw(task) };
168            }
169
170            return;
171        }
172
173        if let Some(tail) = synced.tail {
174            unsafe {
175                tail.set_queue_next(Some(batch_head));
176            }
177        } else {
178            synced.head = Some(batch_head);
179        }
180
181        synced.tail = Some(batch_tail);
182
183        // Increment the count.
184        //
185        // safety: All updates to the len atomic are guarded by the mutex. As
186        // such, a non-atomic load followed by a store is safe.
187        let len = unsafe { self.shared.len.unsync_load() };
188
189        self.shared.len.store(len + num, Release);
190    }
191
192    /// Pops up to `n` values from the queue, passing an iterator over them to
193    /// `f`. The queue lock is held while `f` runs, so any values `f` does not
194    /// consume are removed from the queue and dropped before the lock is
195    /// released.
196    pub(crate) fn pop_n<R>(&self, n: usize, f: impl FnOnce(Pop<'_, T>) -> R) -> R {
197        let mut synced = self.synced.lock();
198        // safety: passing correct `Synced`
199        f(unsafe { self.shared.pop_n(&mut synced, n) })
200    }
201
202    /// Pops every task from the queue into `dst`, holding the queue lock for
203    /// the entire drain so it is atomic with respect to concurrent pushes.
204    #[cfg(all(tokio_unstable, feature = "taskdump"))]
205    pub(crate) fn drain_into(&self, dst: &mut Vec<task::Notified<T>>) {
206        let mut synced = self.synced.lock();
207        // safety: passing correct `Synced`
208        while let Some(task) = unsafe { self.shared.pop(&mut synced) } {
209            dst.push(task);
210        }
211    }
212}