use alloc::{rc::Rc, sync::Arc};
use core::{marker::PhantomData, mem::ManuallyDrop, ptr, sync::atomic::Ordering};
use super::{ThreadCore, ThreadWakeHandle};
#[must_use = "selected threads must be woken after releasing the domain lock"]
pub struct ThreadWakeBatch {
head: *const ThreadCore,
tail: *const ThreadCore,
len: usize,
_task_context: PhantomData<Rc<()>>,
}
impl ThreadWakeBatch {
pub const fn new() -> Self {
Self {
head: ptr::null(),
tail: ptr::null(),
len: 0,
_task_context: PhantomData,
}
}
pub fn push(&mut self, wake: ThreadWakeHandle) -> bool {
let core = &wake.core;
if core
.wake_batch_linked
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return false;
}
let raw = Self::into_raw(wake);
unsafe {
(*raw)
.wake_batch_next
.store(ptr::null_mut(), Ordering::Relaxed);
if self.tail.is_null() {
self.head = raw;
} else {
(*self.tail)
.wake_batch_next
.store(raw.cast_mut(), Ordering::Release);
}
}
self.tail = raw;
self.len += 1;
true
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
pub fn wake_all(mut self) -> usize {
let count = self.len;
while let Some(wake) = self.pop() {
let _result = wake.wake();
}
count
}
fn into_raw(wake: ThreadWakeHandle) -> *const ThreadCore {
let mut wake = ManuallyDrop::new(wake);
let core = unsafe {
ManuallyDrop::take(&mut wake.core)
};
let reap_signal = unsafe {
ptr::read(&wake.reap_signal)
};
drop(reap_signal);
Arc::into_raw(core)
}
unsafe fn from_raw(raw: *const ThreadCore) -> ThreadWakeHandle {
let core = unsafe {
Arc::from_raw(raw)
};
let reap_signal = Arc::clone(&core.reap_signal);
ThreadWakeHandle {
core: ManuallyDrop::new(core),
reap_signal,
}
}
fn pop(&mut self) -> Option<ThreadWakeHandle> {
let raw = self.head;
if raw.is_null() {
return None;
}
let next = unsafe {
(*raw).wake_batch_next.load(Ordering::Acquire).cast_const()
};
self.head = next;
if next.is_null() {
self.tail = ptr::null();
}
self.len -= 1;
unsafe {
(*raw)
.wake_batch_next
.store(ptr::null_mut(), Ordering::Relaxed);
(*raw).wake_batch_linked.store(false, Ordering::Release);
Some(Self::from_raw(raw))
}
}
}
impl Default for ThreadWakeBatch {
fn default() -> Self {
Self::new()
}
}
impl Drop for ThreadWakeBatch {
fn drop(&mut self) {
let was_empty = self.is_empty();
while let Some(wake) = self.pop() {
drop(wake);
}
debug_assert!(was_empty, "thread wake batch was not drained");
}
}