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}