use std::cell::UnsafeCell;
use std::fmt;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::sync::{
atomic::Ordering::{AcqRel, Acquire, Relaxed, Release},
Arc,
};
pub struct Local<T: 'static> {
inner: Arc<Inner<T>>,
}
pub struct Steal<T: 'static>(Arc<Inner<T>>);
pub struct Inner<T: 'static> {
head: AtomicU32,
tail: AtomicU16,
buffer: Box<[UnsafeCell<MaybeUninit<T>>; LOCAL_QUEUE_CAPACITY]>,
}
impl<T> Drop for Inner<T> {
fn drop(&mut self) {
let head = unpack(self.head.load(Relaxed)).0;
let tail = self.tail.load(Relaxed);
let count = tail.wrapping_sub(head);
for offset in 0..count {
let idx = head.wrapping_add(offset) as usize & MASK;
drop(unsafe { self.buffer[idx].get().read().assume_init() });
}
}
}
const LOCAL_QUEUE_CAPACITY: usize = 256;
const MASK: usize = LOCAL_QUEUE_CAPACITY - 1;
const MAX_BATCH_SIZE: u16 = 32;
#[derive(Debug, Clone, PartialEq)]
pub enum StealError {
Empty,
Busy,
}
fn make_fixed_size<T>(buffer: Box<[T]>) -> Box<[T; LOCAL_QUEUE_CAPACITY]> {
assert_eq!(buffer.len(), LOCAL_QUEUE_CAPACITY);
unsafe { Box::from_raw(Box::into_raw(buffer).cast()) }
}
impl<T> Local<T> {
pub fn new() -> Self {
let mut buffer = Vec::with_capacity(LOCAL_QUEUE_CAPACITY);
for _ in 0..LOCAL_QUEUE_CAPACITY {
buffer.push(UnsafeCell::new(MaybeUninit::uninit()));
}
let inner = Arc::new(Inner {
head: AtomicU32::new(0),
tail: AtomicU16::new(0),
buffer: make_fixed_size(buffer.into_boxed_slice()),
});
Local { inner }
}
pub fn stealer(&self) -> Steal<T> {
Steal(self.inner.clone())
}
pub fn push_back(&self, task: T) -> Result<(), T> {
let head = self.inner.head.load(Acquire);
let steal = unpack(head).0;
let tail = unsafe { self.inner.tail.unsync_load() };
if tail.wrapping_sub(steal) >= LOCAL_QUEUE_CAPACITY as u16 {
return Err(task);
}
let idx = tail as usize & MASK;
unsafe { self.inner.buffer[idx].get().write(MaybeUninit::new(task)) };
self.inner.tail.store(tail.wrapping_add(1), Release);
Ok(())
}
pub fn pop(&self) -> Option<T> {
let mut head = self.inner.head.load(Acquire);
let idx = loop {
let (steal, real) = unpack(head);
let tail = unsafe { self.inner.tail.unsync_load() };
if real == tail {
return None;
}
let next_real = real.wrapping_add(1);
let next = if steal == real {
pack(next_real, next_real)
} else {
assert_ne!(steal, next_real);
pack(steal, next_real)
};
let res = self
.inner
.head
.compare_exchange(head, next, AcqRel, Acquire);
match res {
Ok(_) => break real as usize & MASK,
Err(actual) => head = actual,
}
};
Some(unsafe { self.inner.buffer[idx].get().read().assume_init() })
}
}
unsafe impl<T> Send for Local<T> {}
impl<T> Steal<T> {
pub fn steal_into(&self, dst: &Local<T>) -> Result<T, StealError> {
let dst_tail = unsafe { dst.inner.tail.unsync_load() };
let (steal, _) = unpack(dst.inner.head.load(Acquire));
let dest_free_capacity = dst_tail.wrapping_sub(steal);
let (ret, mut n) =
self.steal_into2(dst, dst_tail, (dest_free_capacity + 1).min(MAX_BATCH_SIZE))?;
n -= 1;
dst.inner.tail.store(dst_tail.wrapping_add(n), Release);
Ok(ret)
}
fn steal_into2(
&self,
dst: &Local<T>,
dst_tail: u16,
max_tasks: u16,
) -> Result<(T, u16), StealError> {
let mut prev_packed = self.0.head.load(Acquire);
let mut next_packed;
let n = loop {
let (src_head_steal, src_head_real) = unpack(prev_packed);
let src_tail = self.0.tail.load(Acquire);
if src_head_steal != src_head_real {
return Err(StealError::Busy);
}
let n = src_tail.wrapping_sub(src_head_real);
let n = (n - n / 2).min(max_tasks);
if n == 0 {
return Err(StealError::Empty);
}
let steal_to = src_head_real.wrapping_add(n);
assert_ne!(src_head_steal, steal_to);
next_packed = pack(src_head_steal, steal_to);
let res = self
.0
.head
.compare_exchange(prev_packed, next_packed, Acquire, Acquire);
match res {
Ok(_) => break n,
Err(actual) => prev_packed = actual,
}
};
assert!(n <= LOCAL_QUEUE_CAPACITY as u16 / 2, "actual = {}", n);
let (first, _) = unpack(next_packed);
for i in 0..(n - 1) {
let src_pos = first.wrapping_add(i);
let dst_pos = dst_tail.wrapping_add(i);
let src_idx = src_pos as usize & MASK;
let dst_idx = dst_pos as usize & MASK;
let task = unsafe { self.0.buffer[src_idx].get().read().assume_init() };
unsafe {
dst.inner.buffer[dst_idx]
.get()
.write(MaybeUninit::new(task))
};
}
let src_idx = first.wrapping_add(n - 1) as usize & MASK;
let ret = unsafe { self.0.buffer[src_idx].get().read().assume_init() };
let mut prev_packed = next_packed;
loop {
let head = unpack(prev_packed).1;
next_packed = pack(head, head);
let res = self
.0
.head
.compare_exchange(prev_packed, next_packed, AcqRel, Acquire);
match res {
Ok(_) => return Ok((ret, n)),
Err(actual) => {
let (actual_steal, actual_real) = unpack(actual);
assert_ne!(actual_steal, actual_real);
prev_packed = actual;
}
}
}
}
}
unsafe impl<T> Send for Steal<T> {}
unsafe impl<T> Sync for Steal<T> {}
impl<T> Clone for Steal<T> {
fn clone(&self) -> Steal<T> {
Steal(self.0.clone())
}
}
fn unpack(n: u32) -> (u16, u16) {
let real = n & u16::MAX as u32;
let steal = n >> 16;
(steal as u16, real as u16)
}
fn pack(steal: u16, real: u16) -> u32 {
(real as u32) | ((steal as u32) << 16)
}
#[test]
fn test_local_queue_capacity() {
assert!(LOCAL_QUEUE_CAPACITY - 1 <= u8::MAX as usize);
}
pub(crate) struct AtomicU16 {
inner: UnsafeCell<std::sync::atomic::AtomicU16>,
}
unsafe impl Send for AtomicU16 {}
unsafe impl Sync for AtomicU16 {}
impl AtomicU16 {
pub(crate) const fn new(val: u16) -> AtomicU16 {
let inner = UnsafeCell::new(std::sync::atomic::AtomicU16::new(val));
AtomicU16 { inner }
}
pub(crate) unsafe fn unsync_load(&self) -> u16 {
*(*self.inner.get()).get_mut()
}
}
impl Deref for AtomicU16 {
type Target = std::sync::atomic::AtomicU16;
fn deref(&self) -> &Self::Target {
unsafe { &*self.inner.get() }
}
}
impl fmt::Debug for AtomicU16 {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(fmt)
}
}
pub(crate) struct AtomicU32 {
inner: UnsafeCell<std::sync::atomic::AtomicU32>,
}
unsafe impl Send for AtomicU32 {}
unsafe impl Sync for AtomicU32 {}
impl AtomicU32 {
pub(crate) const fn new(val: u32) -> AtomicU32 {
let inner = UnsafeCell::new(std::sync::atomic::AtomicU32::new(val));
AtomicU32 { inner }
}
}
impl Deref for AtomicU32 {
type Target = std::sync::atomic::AtomicU32;
fn deref(&self) -> &Self::Target {
unsafe { &*self.inner.get() }
}
}
impl fmt::Debug for AtomicU32 {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(fmt)
}
}