mod coroutine;
mod inbox;
mod waker;
use alloc::{boxed::Box, rc::Rc, sync::Arc};
use core::{
cell::{Cell, RefCell},
fmt,
future::Future,
marker::PhantomData,
ptr,
sync::atomic::{AtomicUsize, Ordering},
task::{Context, Poll, Waker},
};
pub use coroutine::{CoroutineHeader, CoroutineId};
use self::{
coroutine::{COMPLETE, Coroutine, POLLING, RUN_QUEUED, release_reference, retain_reference},
inbox::{InboxKind, IntrusiveInbox},
waker::coroutine_waker,
};
use crate::{
runtime::{cpu::IrqGuardToken, task_runtime},
thread::{TaskError, ThreadId, ThreadWakeHandle},
};
pub const DEFAULT_POLL_BATCH: usize = 64;
pub fn wake_waker_sync(waker: Waker) {
debug_assert!(!task_runtime::in_hard_irq());
waker::wake_sync(waker);
}
const NOTIFIED: usize = 1 << 0;
const PARKING: usize = 1 << 1;
const PARKED: usize = 1 << 2;
pub struct LocalExecutor {
shared: Arc<SharedExecutor>,
ready_pending: Cell<*mut CoroutineHeader>,
active: Cell<*mut CoroutineHeader>,
next_generation: Cell<u64>,
_owner_thread_only: PhantomData<Rc<()>>,
}
impl LocalExecutor {
pub fn new(owner_wake: ThreadWakeHandle) -> Result<Self, TaskError> {
if crate::runtime::task_runtime::in_hard_irq() {
return Err(TaskError::UnsafeContext);
}
let expected = owner_wake.thread_id();
let actual = crate::thread::current::current_thread_id()?;
if actual != expected {
return Err(TaskError::ExecutorOwnerMismatch {
expected: expected.as_u64(),
actual: actual.as_u64(),
});
}
Ok(Self {
shared: Arc::new(SharedExecutor::new(owner_wake)),
ready_pending: Cell::new(ptr::null_mut()),
active: Cell::new(ptr::null_mut()),
next_generation: Cell::new(1),
_owner_thread_only: PhantomData,
})
}
pub fn owner_thread(&self) -> ThreadId {
self.shared.owner_thread
}
pub fn spawn<F>(&self, future: F) -> CoroutineId
where
F: Future<Output = ()> + 'static,
{
self.assert_owner_context();
unsafe {
self.spawn_scoped(future).1
}
}
pub fn run<F, P>(&self, future: F, mut park: P) -> F::Output
where
F: Future,
P: FnMut(&ExecutorParkCondition<'_>),
{
self.assert_owner_context();
let output = RefCell::new(None);
let root = async {
output.replace(Some(future.await));
};
let (header, _) = unsafe {
self.spawn_scoped(root)
};
retain_reference(unsafe {
&*header
});
let guard = ScopedRunGuard {
executor: self,
header,
};
while output.borrow().is_none() {
let batch = self.run_ready_batch();
if output.borrow().is_some() || batch.has_more() {
continue;
}
let Some(token) = self.prepare_park() else {
continue;
};
let condition = ExecutorParkCondition { executor: self };
park(&condition);
let _owner_work = token.finish();
unsafe {
coroutine::schedule(header);
}
}
let result = output
.borrow_mut()
.take()
.unwrap_or_else(|| unreachable!("completed root future must publish output"));
drop(guard);
result
}
pub fn run_ready_batch(&self) -> PollBatch {
self.assert_owner_context();
self.shared
.park_state
.fetch_and(!NOTIFIED, Ordering::AcqRel);
let mut cursor = self.take_ready_snapshot();
let mut polled = 0;
let mut completed = 0;
while !cursor.is_null() && polled < DEFAULT_POLL_BATCH {
let header = cursor;
cursor = unsafe {
IntrusiveInbox::take_next(header, InboxKind::Ready)
};
let did_complete = unsafe {
self.poll_ready_coroutine(header)
};
polled += usize::from(did_complete.was_polled());
completed += usize::from(did_complete.was_completed());
}
self.ready_pending.set(cursor);
PollBatch {
polled,
completed,
has_more: self.has_ready(),
}
}
pub fn has_ready(&self) -> bool {
!self.ready_pending.get().is_null() || !self.shared.ready.is_empty()
}
pub fn prepare_park(&self) -> Option<ParkToken<'_>> {
self.assert_owner_context();
if self.has_owner_work() {
return None;
}
let previous = self.shared.park_state.fetch_or(PARKING, Ordering::AcqRel);
if previous & (NOTIFIED | PARKING | PARKED) != 0 || self.has_owner_work() {
self.cancel_park_attempt();
return None;
}
if self
.shared
.park_state
.compare_exchange(PARKING, PARKED, Ordering::AcqRel, Ordering::Acquire)
.is_err()
|| self.has_owner_work()
{
self.cancel_park_attempt();
return None;
}
Some(ParkToken {
executor: self,
active: true,
_owner_thread_only: PhantomData,
})
}
fn has_owner_work(&self) -> bool {
self.has_ready()
}
fn cancel_park_attempt(&self) {
self.shared
.park_state
.fetch_and(!(PARKING | PARKED | NOTIFIED), Ordering::AcqRel);
}
fn finish_park(&self) -> bool {
let state = self.shared.park_state.swap(0, Ordering::AcqRel);
state & NOTIFIED != 0 || self.has_owner_work()
}
fn assert_owner_context(&self) {
if crate::runtime::task_runtime::in_hard_irq() {
crate::runtime::task_runtime::fatal_invariant(
0x4558_0005,
self.owner_thread().as_u64() as usize,
);
}
match crate::thread::current::current_thread_id() {
Ok(actual) if actual == self.owner_thread() => {}
Ok(actual) => {
crate::runtime::task_runtime::fatal_invariant(0x4558_0003, actual.as_u64() as usize)
}
Err(_) => crate::runtime::task_runtime::fatal_invariant(
0x4558_0006,
self.owner_thread().as_u64() as usize,
),
}
}
}
pub struct ExecutorParkCondition<'executor> {
executor: &'executor LocalExecutor,
}
impl ExecutorParkCondition<'_> {
pub fn should_abort(&self) -> bool {
self.executor.shared.park_state.load(Ordering::Acquire) & NOTIFIED != 0
|| self.executor.has_owner_work()
}
}
impl fmt::Debug for ExecutorParkCondition<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ExecutorParkCondition")
.field("owner_thread", &self.executor.owner_thread())
.field("should_abort", &self.should_abort())
.finish()
}
}
impl Drop for LocalExecutor {
fn drop(&mut self) {
self.assert_owner_context();
self.shutdown();
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PollBatch {
polled: usize,
completed: usize,
has_more: bool,
}
impl PollBatch {
pub const fn polled(self) -> usize {
self.polled
}
pub const fn completed(self) -> usize {
self.completed
}
pub const fn has_more(self) -> bool {
self.has_more
}
}
#[must_use = "the token must be held across the task-system park operation"]
pub struct ParkToken<'executor> {
executor: &'executor LocalExecutor,
active: bool,
_owner_thread_only: PhantomData<Rc<()>>,
}
impl ParkToken<'_> {
pub fn finish(mut self) -> bool {
self.active = false;
self.executor.finish_park()
}
}
impl Drop for ParkToken<'_> {
fn drop(&mut self) {
if self.active {
self.executor.cancel_park_attempt();
}
}
}
pub(super) struct SharedExecutor {
owner_thread: ThreadId,
owner_wake: ThreadWakeHandle,
ready: IntrusiveInbox,
park_state: AtomicUsize,
ready_publication: AtomicUsize,
}
const READY_PUBLICATION_CLOSED: usize = 1usize << (usize::BITS - 1);
const READY_PUBLISHER_COUNT_MASK: usize = READY_PUBLICATION_CLOSED - 1;
impl SharedExecutor {
fn new(owner_wake: ThreadWakeHandle) -> Self {
Self {
owner_thread: owner_wake.thread_id(),
owner_wake,
ready: IntrusiveInbox::new(InboxKind::Ready),
park_state: AtomicUsize::new(0),
ready_publication: AtomicUsize::new(0),
}
}
pub(super) fn publish_ready(
&self,
header: *mut CoroutineHeader,
intent: crate::thread::WakeIntent,
) -> bool {
let Some(_publisher) = self.begin_ready_publish_guard() else {
return false;
};
unsafe {
self.ready.push(header);
}
self.notify_owner(intent);
true
}
fn begin_ready_publish_guard(&self) -> Option<ReadyPublishGuard<'_>> {
let irq_token = crate::runtime::enter_irq_guard(crate::runtime::IrqGuardSource::Executor);
if self.begin_ready_publish() {
Some(ReadyPublishGuard {
executor: self,
irq_token,
_not_send: PhantomData,
})
} else {
unsafe { task_runtime::irq_guard_exit(irq_token) };
None
}
}
fn begin_ready_publish(&self) -> bool {
let mut state = self.ready_publication.load(Ordering::Acquire);
loop {
if state & READY_PUBLICATION_CLOSED != 0 {
return false;
}
if state & READY_PUBLISHER_COUNT_MASK == READY_PUBLISHER_COUNT_MASK {
crate::runtime::task_runtime::fatal_invariant(
0x4558_0007,
self.owner_thread.as_u64() as usize,
);
}
match self.ready_publication.compare_exchange_weak(
state,
state + 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return true,
Err(updated) => state = updated,
}
}
}
fn finish_ready_publish(&self) {
let previous = self.ready_publication.fetch_sub(1, Ordering::Release);
debug_assert_ne!(previous & READY_PUBLISHER_COUNT_MASK, 0);
}
fn notify_owner(&self, intent: crate::thread::WakeIntent) {
let previous = self.park_state.fetch_or(NOTIFIED, Ordering::AcqRel);
if previous & PARKED != 0 {
let _result = if intent.is_sync() {
self.owner_wake.wake_sync()
} else {
self.owner_wake.wake()
};
}
}
fn close_and_wait_for_publishers(&self) {
self.ready_publication
.fetch_or(READY_PUBLICATION_CLOSED, Ordering::AcqRel);
while self.ready_publication.load(Ordering::Acquire) != READY_PUBLICATION_CLOSED {
core::hint::spin_loop();
}
}
}
struct ReadyPublishGuard<'executor> {
executor: &'executor SharedExecutor,
irq_token: IrqGuardToken,
_not_send: PhantomData<*mut ()>,
}
impl Drop for ReadyPublishGuard<'_> {
fn drop(&mut self) {
self.executor.finish_ready_publish();
unsafe { task_runtime::irq_guard_exit(self.irq_token) };
}
}
struct ScopedRunGuard<'executor> {
executor: &'executor LocalExecutor,
header: *mut CoroutineHeader,
}
struct ReadyQueueReference {
header: *mut CoroutineHeader,
polling: bool,
}
struct OwnedCoroutineReference {
header: *mut CoroutineHeader,
}
impl OwnedCoroutineReference {
unsafe fn new(header: *mut CoroutineHeader) -> Self {
Self { header }
}
}
impl Drop for OwnedCoroutineReference {
fn drop(&mut self) {
unsafe {
release_reference(self.header);
}
}
}
impl ReadyQueueReference {
const fn new(header: *mut CoroutineHeader) -> Self {
Self {
header,
polling: false,
}
}
fn mark_polling(&mut self) {
self.polling = true;
}
fn finish_polling(&mut self) {
unsafe {
(*self.header).state.fetch_and(!POLLING, Ordering::AcqRel);
}
self.polling = false;
}
}
impl Drop for ReadyQueueReference {
fn drop(&mut self) {
if self.polling {
unsafe {
(*self.header).state.fetch_and(!POLLING, Ordering::AcqRel);
}
}
unsafe {
release_reference(self.header);
}
}
}
impl Drop for ScopedRunGuard<'_> {
fn drop(&mut self) {
let _scoped_reference = unsafe {
OwnedCoroutineReference::new(self.header)
};
self.executor.cancel_coroutine(self.header);
}
}
#[derive(Clone, Copy)]
enum PollDisposition {
Skipped,
Pending,
Completed,
}
impl PollDisposition {
const fn was_polled(self) -> bool {
!matches!(self, Self::Skipped)
}
const fn was_completed(self) -> bool {
matches!(self, Self::Completed)
}
}
mod polling;
mod membership;
mod shutdown;
mod block_on;
pub use block_on::{BlockOnError, block_on, block_on_timeout};