use crate::fiber::inner::{OwnedList, Schedule, Fiber, TransferStack};
use std::{
cell::UnsafeCell,
collections::VecDeque,
fmt,
sync::{Mutex, MutexGuard},
};
pub(crate) struct MpscQueues<S: 'static> {
owned_tasks: UnsafeCell<OwnedList<S>>,
local_queue: UnsafeCell<VecDeque<Fiber<S>>>,
remote_queue: Mutex<RemoteQueue<S>>,
pending_drop: TransferStack<S>,
}
pub(crate) struct RemoteQueue<S: 'static> {
queue: VecDeque<Fiber<S>>,
open: bool,
}
impl<S> MpscQueues<S>
where
S: Schedule + 'static,
{
pub(crate) const INITIAL_CAPACITY: usize = 64;
pub(crate) const CHECK_REMOTE_INTERVAL: u8 = 13;
pub(crate) fn new() -> Self {
Self {
owned_tasks: UnsafeCell::new(OwnedList::new()),
local_queue: UnsafeCell::new(VecDeque::with_capacity(Self::INITIAL_CAPACITY)),
pending_drop: TransferStack::new(),
remote_queue: Mutex::new(RemoteQueue {
queue: VecDeque::with_capacity(Self::INITIAL_CAPACITY),
open: true,
}),
}
}
pub(crate) unsafe fn add_task(&self, task: &Fiber<S>) {
(*self.owned_tasks.get()).insert(task);
}
pub(crate) unsafe fn push_local(&self, task: Fiber<S>) {
(*self.local_queue.get()).push_back(task);
}
pub(crate) unsafe fn release_local(&self, task: &Fiber<S>) {
(*self.owned_tasks.get()).remove(task);
}
pub(crate) fn remote(&self) -> MutexGuard<'_, RemoteQueue<S>> {
self.remote_queue
.lock()
.expect("failed to lock remote queue")
}
pub(crate) fn release_remote(&self, task: Fiber<S>) {
self.pending_drop.push(task);
}
pub(crate) unsafe fn next_task(&self, tick: u8) -> Option<Fiber<S>> {
if 0 == tick % Self::CHECK_REMOTE_INTERVAL {
self.next_remote_task().or_else(|| self.next_local_task())
} else {
self.next_local_task().or_else(|| self.next_remote_task())
}
}
pub(crate) unsafe fn next_local_task(&self) -> Option<Fiber<S>> {
(*self.local_queue.get()).pop_front()
}
pub(crate) fn next_remote_task(&self) -> Option<Fiber<S>> {
#[allow(clippy::match_wild_err_arm)]
let mut lock = match self.remote_queue.lock() {
Err(_) if std::thread::panicking() => return None,
Err(_) => panic!("mutex poisoned"),
Ok(lock) => lock,
};
lock.queue.pop_front()
}
pub(crate) unsafe fn has_tasks_remaining(&self) -> bool {
!(*self.owned_tasks.get()).is_empty()
}
pub(crate) unsafe fn drain_pending_drop(&self) {
for task in self.pending_drop.drain() {
(*self.owned_tasks.get()).remove(&task);
drop(task);
}
}
pub(crate) unsafe fn shutdown(&self) {
self.close_remote();
self.close_local();
self.shutdown_owned_tasks();
self.drain_pending_drop();
}
pub(crate) unsafe fn drain_queues(&self) {
self.close_local();
self.close_remote();
}
unsafe fn shutdown_owned_tasks(&self) {
(*self.owned_tasks.get()).shutdown();
}
fn close_remote(&self) {
#[allow(clippy::match_wild_err_arm)]
let mut lock = match self.remote_queue.lock() {
Err(_) if std::thread::panicking() => return,
Err(_) => panic!("mutex poisoned"),
Ok(lock) => lock,
};
lock.open = false;
while let Some(task) = lock.queue.pop_front() {
task.shutdown();
}
}
unsafe fn close_local(&self) {
while let Some(task) = self.next_local_task() {
task.shutdown();
}
}
}
impl<S> fmt::Debug for MpscQueues<S> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("MpscQueues")
.field("owned_tasks", &self.owned_tasks)
.field("remote_queue", &self.remote_queue)
.field("local_queue", &self.local_queue)
.finish()
}
}
impl<S> RemoteQueue<S>
where
S: Schedule,
{
pub(crate) fn schedule(&mut self, task: Fiber<S>, spawn: bool) {
if !spawn || self.open {
self.queue.push_back(task);
} else {
task.shutdown();
}
}
}
impl<S> fmt::Debug for RemoteQueue<S> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("RemoteQueue")
.field("queue", &self.queue)
.field("open", &self.open)
.finish()
}
}