mod message;
mod node;
use core::{
ptr,
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
};
pub use message::{InboxKind, InboxMessage, InboxOperation};
pub use node::InboxNode;
use crate::runtime::delivery::epoch::EpochMpscQueue;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublishResult {
Published,
AlreadyPending,
WrongKind,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DrainBatch {
drained: usize,
pending: bool,
}
impl DrainBatch {
pub const fn drained(self) -> usize {
self.drained
}
pub const fn pending(self) -> bool {
self.pending
}
}
#[derive(Debug)]
pub struct SchedulerInbox {
kind: InboxKind,
publication: EpochMpscQueue<InboxNode>,
pending: AtomicPtr<InboxNode>,
draining: AtomicBool,
}
impl SchedulerInbox {
pub const fn new(kind: InboxKind) -> Self {
Self {
kind,
publication: EpochMpscQueue::new(),
pending: AtomicPtr::new(ptr::null_mut()),
draining: AtomicBool::new(false),
}
}
pub fn publish(
&self,
node: core::pin::Pin<&'static InboxNode>,
message: InboxMessage,
) -> PublishResult {
self.publish_with_head_transition(node, message).0
}
pub(crate) fn publish_with_head_transition(
&self,
node: core::pin::Pin<&'static InboxNode>,
message: InboxMessage,
) -> (PublishResult, bool) {
if node.kind() != self.kind || message.kind() != self.kind {
return (PublishResult::WrongKind, false);
}
if !node.reserve(message) {
return (PublishResult::AlreadyPending, false);
}
let node = node.get_ref() as *const InboxNode as *mut InboxNode;
let transitioned = unsafe {
self.publication.publish(node, (*node).next())
};
(PublishResult::Published, transitioned)
}
pub fn drain(&self, limit: usize, output: &mut [InboxMessage]) -> DrainBatch {
if self
.draining
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return DrainBatch {
drained: 0,
pending: true,
};
}
let mut cursor = self.take_snapshot();
let bound = limit.min(output.len());
let mut drained = 0;
while !cursor.is_null() && drained < bound {
let node = cursor;
cursor = unsafe {
(*node).take_next()
};
output[drained] = unsafe {
(*node).take_message()
};
drained += 1;
}
self.pending.store(cursor, Ordering::Release);
let pending = !cursor.is_null() || !self.publication.is_empty();
self.draining.store(false, Ordering::Release);
DrainBatch { drained, pending }
}
pub fn has_pending(&self) -> bool {
!self.pending.load(Ordering::Acquire).is_null() || !self.publication.is_empty()
}
fn take_snapshot(&self) -> *mut InboxNode {
let pending = self.pending.swap(ptr::null_mut(), Ordering::Acquire);
if !pending.is_null() {
return pending;
}
let stack = unsafe {
self.publication.take_graced_stack()
};
unsafe {
reverse(stack)
}
}
}
unsafe fn reverse(mut cursor: *mut InboxNode) -> *mut InboxNode {
let mut reversed = ptr::null_mut();
while !cursor.is_null() {
let next = unsafe {
(*cursor).next().load(Ordering::Relaxed)
};
unsafe {
(*cursor).next().store(reversed, Ordering::Relaxed);
}
reversed = cursor;
cursor = next;
}
reversed
}