Skip to main content

tokio/runtime/scheduler/multi_thread/
queue.rs

1//! Run-queue structures to support a work-stealing scheduler
2
3use crate::loom::cell::UnsafeCell;
4use crate::loom::sync::Arc;
5use crate::runtime::scheduler::multi_thread::{Overflow, Stats};
6use crate::runtime::task;
7
8use std::mem::{self, MaybeUninit};
9use std::ptr;
10use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
11
12// Use wider integers when possible to increase ABA resilience.
13//
14// See issue #5041: <https://github.com/tokio-rs/tokio/issues/5041>.
15cfg_has_atomic_u64! {
16    type UnsignedShort = u32;
17    type UnsignedLong = u64;
18    type AtomicUnsignedShort = crate::loom::sync::atomic::AtomicU32;
19    type AtomicUnsignedLong = crate::loom::sync::atomic::AtomicU64;
20}
21cfg_not_has_atomic_u64! {
22    type UnsignedShort = u16;
23    type UnsignedLong = u32;
24    type AtomicUnsignedShort = crate::loom::sync::atomic::AtomicU16;
25    type AtomicUnsignedLong = crate::loom::sync::atomic::AtomicU32;
26}
27
28/// Producer handle. May only be used from a single thread.
29pub(crate) struct Local<T: 'static> {
30    inner: Arc<Inner<T>>,
31}
32
33/// Consumer handle. May be used from many threads.
34pub(crate) struct Steal<T: 'static>(Arc<Inner<T>>);
35
36pub(crate) struct Inner<T: 'static> {
37    /// Concurrently updated by many threads.
38    ///
39    /// Contains two `UnsignedShort` values. The `LSB` byte is the "real" head of
40    /// the queue. The `UnsignedShort` in the `MSB` is set by a stealer in process
41    /// of stealing values. It represents the first value being stolen in the
42    /// batch. The `UnsignedShort` indices are intentionally wider than strictly
43    /// required for buffer indexing in order to provide ABA mitigation and make
44    /// it possible to distinguish between full and empty buffers.
45    ///
46    /// When both `UnsignedShort` values are the same, there is no active
47    /// stealer.
48    ///
49    /// Tracking an in-progress stealer prevents a wrapping scenario.
50    head: AtomicUnsignedLong,
51
52    /// Only updated by producer thread but read by many threads.
53    tail: AtomicUnsignedShort,
54
55    /// Elements
56    buffer: Box<[UnsafeCell<MaybeUninit<task::Notified<T>>>; LOCAL_QUEUE_CAPACITY]>,
57}
58
59unsafe impl<T> Send for Inner<T> {}
60unsafe impl<T> Sync for Inner<T> {}
61
62#[cfg(not(loom))]
63const LOCAL_QUEUE_CAPACITY: usize = 256;
64
65// Shrink the size of the local queue when using loom. This shouldn't impact
66// logic, but allows loom to test more edge cases in a reasonable a mount of
67// time.
68#[cfg(loom)]
69const LOCAL_QUEUE_CAPACITY: usize = 4;
70
71const MASK: usize = LOCAL_QUEUE_CAPACITY - 1;
72
73// Constructing the fixed size array directly is very awkward. The only way to
74// do it is to repeat `UnsafeCell::new(MaybeUninit::uninit())` 256 times, as
75// the contents are not Copy. The trick with defining a const doesn't work for
76// generic types.
77fn make_fixed_size<T>(buffer: Box<[T]>) -> Box<[T; LOCAL_QUEUE_CAPACITY]> {
78    assert_eq!(buffer.len(), LOCAL_QUEUE_CAPACITY);
79
80    // safety: We check that the length is correct.
81    unsafe { Box::from_raw(Box::into_raw(buffer).cast()) }
82}
83
84/// Create a new local run-queue
85pub(crate) fn local<T: 'static>() -> (Steal<T>, Local<T>) {
86    let buffer = std::iter::repeat_with(|| UnsafeCell::new(MaybeUninit::uninit()));
87
88    let inner = Arc::new(Inner {
89        head: AtomicUnsignedLong::new(0),
90        tail: AtomicUnsignedShort::new(0),
91        buffer: make_fixed_size(buffer.take(LOCAL_QUEUE_CAPACITY).collect()),
92    });
93
94    let local = Local {
95        inner: inner.clone(),
96    };
97
98    let remote = Steal(inner);
99
100    (remote, local)
101}
102
103impl<T> Local<T> {
104    /// Returns the number of entries in the queue
105    pub(crate) fn len(&self) -> usize {
106        let (_, head) = unpack(self.inner.head.load(Acquire));
107        // safety: this is the **only** thread that updates this cell.
108        let tail = unsafe { self.inner.tail.unsync_load() };
109        len(head, tail)
110    }
111
112    /// How many tasks can be pushed into the queue
113    pub(crate) fn remaining_slots(&self) -> usize {
114        let (steal, _) = unpack(self.inner.head.load(Acquire));
115        // safety: this is the **only** thread that updates this cell.
116        let tail = unsafe { self.inner.tail.unsync_load() };
117
118        LOCAL_QUEUE_CAPACITY - len(steal, tail)
119    }
120
121    pub(crate) fn max_capacity(&self) -> usize {
122        LOCAL_QUEUE_CAPACITY
123    }
124
125    /// Returns false if there are any entries in the queue
126    ///
127    /// Separate to `is_stealable` so that refactors of `is_stealable` to "protect"
128    /// some tasks from stealing won't affect this
129    pub(crate) fn has_tasks(&self) -> bool {
130        self.len() != 0
131    }
132
133    /// Pushes a batch of tasks to the back of the queue. All tasks must fit in
134    /// the local queue.
135    ///
136    /// # Panics
137    ///
138    /// The method panics if there is not enough capacity to fit in the queue.
139    pub(crate) fn push_back(&mut self, tasks: impl ExactSizeIterator<Item = task::Notified<T>>) {
140        let len = tasks.len();
141        assert!(len <= LOCAL_QUEUE_CAPACITY);
142
143        if len == 0 {
144            // Nothing to do
145            return;
146        }
147
148        let head = self.inner.head.load(Acquire);
149        let (steal, _) = unpack(head);
150
151        // safety: this is the **only** thread that updates this cell.
152        let mut tail = unsafe { self.inner.tail.unsync_load() };
153
154        if tail.wrapping_sub(steal) <= (LOCAL_QUEUE_CAPACITY - len) as UnsignedShort {
155            // Yes, this if condition is structured a bit weird (first block
156            // does nothing, second returns an error). It is this way to match
157            // `push_back_or_overflow`.
158        } else {
159            panic!()
160        }
161
162        for task in tasks {
163            let idx = tail as usize & MASK;
164
165            self.inner.buffer[idx].with_mut(|ptr| {
166                // Write the task to the slot
167                //
168                // Safety: There is only one producer and the above `if`
169                // condition ensures we don't touch a cell if there is a
170                // value, thus no consumer.
171                unsafe {
172                    ptr::write((*ptr).as_mut_ptr(), task);
173                }
174            });
175
176            tail = tail.wrapping_add(1);
177        }
178
179        self.inner.tail.store(tail, Release);
180    }
181
182    /// Pushes a task to the back of the local queue, if there is not enough
183    /// capacity in the queue, this triggers the overflow operation.
184    ///
185    /// When the queue overflows, half of the current contents of the queue is
186    /// moved to the given Injection queue. This frees up capacity for more
187    /// tasks to be pushed into the local queue.
188    pub(crate) fn push_back_or_overflow<O: Overflow<T>>(
189        &mut self,
190        mut task: task::Notified<T>,
191        overflow: &O,
192        stats: &mut Stats,
193    ) {
194        let tail = loop {
195            let head = self.inner.head.load(Acquire);
196            let (steal, real) = unpack(head);
197
198            // safety: this is the **only** thread that updates this cell.
199            let tail = unsafe { self.inner.tail.unsync_load() };
200
201            if tail.wrapping_sub(steal) < LOCAL_QUEUE_CAPACITY as UnsignedShort {
202                // There is capacity for the task
203                break tail;
204            } else if steal != real {
205                // Concurrently stealing, this will free up capacity, so only
206                // push the task onto the inject queue
207                overflow.push(task);
208                return;
209            } else {
210                // Push the current task and half of the queue into the
211                // inject queue.
212                match self.push_overflow(task, real, tail, overflow, stats) {
213                    Ok(_) => return,
214                    // Lost the race, try again
215                    Err(v) => {
216                        task = v;
217                    }
218                }
219            }
220        };
221
222        self.push_back_finish(task, tail);
223    }
224
225    // Second half of `push_back`
226    fn push_back_finish(&self, task: task::Notified<T>, tail: UnsignedShort) {
227        // Map the position to a slot index.
228        let idx = tail as usize & MASK;
229
230        self.inner.buffer[idx].with_mut(|ptr| {
231            // Write the task to the slot
232            //
233            // Safety: There is only one producer and the above `if`
234            // condition ensures we don't touch a cell if there is a
235            // value, thus no consumer.
236            unsafe {
237                ptr::write((*ptr).as_mut_ptr(), task);
238            }
239        });
240
241        // Make the task available. Synchronizes with a load in
242        // `steal_into2`.
243        self.inner.tail.store(tail.wrapping_add(1), Release);
244    }
245
246    /// Moves a batch of tasks into the inject queue.
247    ///
248    /// This will temporarily make some of the tasks unavailable to stealers.
249    /// Once `push_overflow` is done, a notification is sent out, so if other
250    /// workers "missed" some of the tasks during a steal, they will get
251    /// another opportunity.
252    #[inline(never)]
253    fn push_overflow<O: Overflow<T>>(
254        &mut self,
255        task: task::Notified<T>,
256        head: UnsignedShort,
257        tail: UnsignedShort,
258        overflow: &O,
259        stats: &mut Stats,
260    ) -> Result<(), task::Notified<T>> {
261        /// How many elements are we taking from the local queue.
262        ///
263        /// This is one less than the number of tasks pushed to the inject
264        /// queue as we are also inserting the `task` argument.
265        const NUM_TASKS_TAKEN: UnsignedShort = (LOCAL_QUEUE_CAPACITY / 2) as UnsignedShort;
266
267        assert_eq!(
268            tail.wrapping_sub(head) as usize,
269            LOCAL_QUEUE_CAPACITY,
270            "queue is not full; tail = {tail}; head = {head}"
271        );
272
273        // Claim all tasks.
274        //
275        // We are claiming the tasks **before** reading them out of the buffer.
276        // This is safe because only the **current** thread is able to push new
277        // tasks.
278        //
279        // There isn't really any need for memory ordering... Relaxed would
280        // work. This is because all tasks are pushed into the queue from the
281        // current thread (or memory has been acquired if the local queue handle
282        // moved).
283        if self
284            .inner
285            .head
286            .compare_exchange_weak(pack(head, head), pack(tail, tail), Release, Relaxed)
287            .is_err()
288        {
289            // We failed to claim the tasks, losing the race. Return out of
290            // this function and try the full `push` routine again. The queue
291            // may not be full anymore.
292            return Err(task);
293        }
294
295        // Add back the first half of tasks.
296        //
297        // We are doing it this way instead of just taking half of the tasks because we want the
298        // *second* half of the tasks, and if you just incremented `head` by `NUM_TASKS_TAKEN`,
299        // then you would be taking the first half instead of the second half.
300        //
301        // Pushing the second half of the local queue to the injection queue is better because when
302        // we take tasks *out* of the injection queue, we always place them in the first half. This
303        // means that if a task is in the second half, then we know for sure that this task is not
304        // a task we just got from the injection queue. This ensures that when we take a task out
305        // of the injection queue, then it will not be moved back into the injection queue (at
306        // least not until after we have polled it at least once).
307        //
308        // Note that if a concurrent worker tries to steal from us between these two operations and
309        // sees that the worker queue is empty, then that worker may go to sleep, and we do not
310        // notify it about these tasks becoming available for stealing again. Ordinarily this would
311        // be a problem, but it isn't in this case because the worker will be notified about the
312        // tasks we are adding to the injection queue instead, which ensures that the stealer wakes
313        // up again to take the tasks from the injection queue.
314        self.inner
315            .tail
316            .store(tail.wrapping_add(NUM_TASKS_TAKEN), Release);
317
318        /// An iterator that takes elements out of the run queue.
319        struct BatchTaskIter<'a, T: 'static> {
320            buffer: &'a [UnsafeCell<MaybeUninit<task::Notified<T>>>; LOCAL_QUEUE_CAPACITY],
321            head: UnsignedLong,
322            i: UnsignedLong,
323        }
324        impl<'a, T: 'static> Iterator for BatchTaskIter<'a, T> {
325            type Item = task::Notified<T>;
326
327            #[inline]
328            fn next(&mut self) -> Option<task::Notified<T>> {
329                if self.i == UnsignedLong::from(NUM_TASKS_TAKEN) {
330                    None
331                } else {
332                    let i_idx = self.i.wrapping_add(self.head) as usize & MASK;
333                    let slot = &self.buffer[i_idx];
334
335                    // safety: Our CAS from before has assumed exclusive ownership
336                    // of the task pointers in this range.
337                    let task = slot.with(|ptr| unsafe { ptr::read((*ptr).as_ptr()) });
338
339                    self.i += 1;
340                    Some(task)
341                }
342            }
343        }
344
345        // safety: The CAS above ensures that no consumer will look at these
346        // values again, and we are the only producer.
347        let batch_iter = BatchTaskIter {
348            buffer: &self.inner.buffer,
349            head: head.wrapping_add(NUM_TASKS_TAKEN) as UnsignedLong,
350            i: 0,
351        };
352        overflow.push_batch(batch_iter.chain(std::iter::once(task)));
353
354        // Add 1 to factor in the task currently being scheduled.
355        stats.incr_overflow_count();
356
357        Ok(())
358    }
359
360    /// Pops a task from the local queue.
361    pub(crate) fn pop(&mut self) -> Option<task::Notified<T>> {
362        let mut head = self.inner.head.load(Acquire);
363
364        let idx = loop {
365            let (steal, real) = unpack(head);
366
367            // safety: this is the **only** thread that updates this cell.
368            let tail = unsafe { self.inner.tail.unsync_load() };
369
370            if real == tail {
371                // queue is empty
372                return None;
373            }
374
375            let next_real = real.wrapping_add(1);
376
377            // If `steal == real` there are no concurrent stealers. Both `steal`
378            // and `real` are updated.
379            let next = if steal == real {
380                pack(next_real, next_real)
381            } else {
382                assert_ne!(steal, next_real);
383                pack(steal, next_real)
384            };
385
386            // Attempt to claim a task.
387            let res = self
388                .inner
389                .head
390                .compare_exchange_weak(head, next, AcqRel, Acquire);
391
392            match res {
393                Ok(_) => break real as usize & MASK,
394                Err(actual) => head = actual,
395            }
396        };
397
398        Some(self.inner.buffer[idx].with(|ptr| unsafe { ptr::read(ptr).assume_init() }))
399    }
400}
401
402impl<T> Steal<T> {
403    /// Returns the number of entries in the queue
404    pub(crate) fn len(&self) -> usize {
405        let (_, head) = unpack(self.0.head.load(Acquire));
406        let tail = self.0.tail.load(Acquire);
407        len(head, tail)
408    }
409
410    /// Return true if the queue is empty,
411    /// false if there are any entries in the queue
412    pub(crate) fn is_empty(&self) -> bool {
413        self.len() == 0
414    }
415
416    /// Steals half the tasks from self and place them into `dst`.
417    pub(crate) fn steal_into(
418        &self,
419        dst: &mut Local<T>,
420        dst_stats: &mut Stats,
421    ) -> Option<task::Notified<T>> {
422        // Safety: the caller is the only thread that mutates `dst.tail` and
423        // holds a mutable reference.
424        let dst_tail = unsafe { dst.inner.tail.unsync_load() };
425
426        // To the caller, `dst` may **look** empty but still have values
427        // contained in the buffer. If another thread is concurrently stealing
428        // from `dst` there may not be enough capacity to steal.
429        let (steal, _) = unpack(dst.inner.head.load(Acquire));
430
431        if dst_tail.wrapping_sub(steal) > LOCAL_QUEUE_CAPACITY as UnsignedShort / 2 {
432            // we *could* try to steal less here, but for simplicity, we're just
433            // going to abort.
434            return None;
435        }
436
437        // Steal the tasks into `dst`'s buffer. This does not yet expose the
438        // tasks in `dst`.
439        let mut n = self.steal_into2(dst, dst_tail);
440
441        if n == 0 {
442            // No tasks were stolen
443            return None;
444        }
445
446        dst_stats.incr_steal_count(n as u16);
447        dst_stats.incr_steal_operations();
448
449        // We are returning a task here
450        n -= 1;
451
452        let ret_pos = dst_tail.wrapping_add(n);
453        let ret_idx = ret_pos as usize & MASK;
454
455        // safety: the value was written as part of `steal_into2` and not
456        // exposed to stealers, so no other thread can access it.
457        let ret = dst.inner.buffer[ret_idx].with(|ptr| unsafe { ptr::read((*ptr).as_ptr()) });
458
459        if n == 0 {
460            // The `dst` queue is empty, but a single task was stolen
461            return Some(ret);
462        }
463
464        // Make the stolen items available to consumers
465        dst.inner.tail.store(dst_tail.wrapping_add(n), Release);
466
467        Some(ret)
468    }
469
470    // Steal tasks from `self`, placing them into `dst`. Returns the number of
471    // tasks that were stolen.
472    fn steal_into2(&self, dst: &mut Local<T>, dst_tail: UnsignedShort) -> UnsignedShort {
473        let mut prev_packed = self.0.head.load(Acquire);
474        let mut next_packed;
475
476        let n = loop {
477            let (src_head_steal, src_head_real) = unpack(prev_packed);
478            let src_tail = self.0.tail.load(Acquire);
479
480            // If these two do not match, another thread is concurrently
481            // stealing from the queue.
482            if src_head_steal != src_head_real {
483                return 0;
484            }
485
486            // Number of available tasks to steal
487            let n = src_tail.wrapping_sub(src_head_real);
488            let n = n - n / 2;
489
490            if n == 0 {
491                // No tasks available to steal
492                return 0;
493            }
494
495            // Update the real head index to acquire the tasks.
496            let steal_to = src_head_real.wrapping_add(n);
497            assert_ne!(src_head_steal, steal_to);
498            next_packed = pack(src_head_steal, steal_to);
499
500            // Claim all those tasks. This is done by incrementing the "real"
501            // head but not the steal. By doing this, no other thread is able to
502            // steal from this queue until the current thread completes.
503            let res = self
504                .0
505                .head
506                .compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
507
508            match res {
509                Ok(_) => break n,
510                Err(actual) => prev_packed = actual,
511            }
512        };
513
514        assert!(
515            n <= LOCAL_QUEUE_CAPACITY as UnsignedShort / 2,
516            "actual = {n}"
517        );
518
519        let (first, _) = unpack(next_packed);
520
521        // Take all the tasks
522        for i in 0..n {
523            // Compute the positions
524            let src_pos = first.wrapping_add(i);
525            let dst_pos = dst_tail.wrapping_add(i);
526
527            // Map to slots
528            let src_idx = src_pos as usize & MASK;
529            let dst_idx = dst_pos as usize & MASK;
530
531            // Read the task
532            //
533            // safety: We acquired the task with the atomic exchange above.
534            let task = self.0.buffer[src_idx].with(|ptr| unsafe { ptr::read((*ptr).as_ptr()) });
535
536            // Write the task to the new slot
537            //
538            // safety: `dst` queue is empty and we are the only producer to
539            // this queue.
540            dst.inner.buffer[dst_idx]
541                .with_mut(|ptr| unsafe { ptr::write((*ptr).as_mut_ptr(), task) });
542        }
543
544        let mut prev_packed = next_packed;
545
546        // Update `src_head_steal` to match `src_head_real` signalling that the
547        // stealing routine is complete.
548        loop {
549            let head = unpack(prev_packed).1;
550            next_packed = pack(head, head);
551
552            let res = self
553                .0
554                .head
555                .compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
556
557            match res {
558                Ok(_) => return n,
559                Err(actual) => prev_packed = actual,
560            }
561        }
562    }
563}
564
565impl<T> Clone for Steal<T> {
566    fn clone(&self) -> Steal<T> {
567        Steal(self.0.clone())
568    }
569}
570
571impl<T> Drop for Local<T> {
572    fn drop(&mut self) {
573        if !std::thread::panicking() {
574            assert!(self.pop().is_none(), "queue not empty");
575        }
576    }
577}
578
579/// Calculate the length of the queue using the head and tail.
580/// The `head` can be the `steal` or `real` head.
581fn len(head: UnsignedShort, tail: UnsignedShort) -> usize {
582    tail.wrapping_sub(head) as usize
583}
584
585/// Split the head value into the real head and the index a stealer is working
586/// on.
587fn unpack(n: UnsignedLong) -> (UnsignedShort, UnsignedShort) {
588    let real = n & UnsignedShort::MAX as UnsignedLong;
589    let steal = n >> (mem::size_of::<UnsignedShort>() * 8);
590
591    (steal as UnsignedShort, real as UnsignedShort)
592}
593
594/// Join the two head values
595fn pack(steal: UnsignedShort, real: UnsignedShort) -> UnsignedLong {
596    (real as UnsignedLong) | ((steal as UnsignedLong) << (mem::size_of::<UnsignedShort>() * 8))
597}
598
599#[test]
600fn test_local_queue_capacity() {
601    assert!(LOCAL_QUEUE_CAPACITY - 1 <= u8::MAX as usize);
602}