Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically Allocated Executor #3558

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions embassy-executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ critical-section = { version = "1.1", features = ["std"] }
## Enable nightly-only features
nightly = ["embassy-executor-macros/nightly"]

alloc = []

# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to
# being an complicated advanced and undocumented feature.
# See: https://github.com/embassy-rs/embassy/pull/1263
Expand Down
10 changes: 5 additions & 5 deletions embassy-executor/src/raw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub(crate) struct TaskHeader {
pub(crate) state: State,
pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
pub(crate) poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,

#[cfg(feature = "integrated-timers")]
pub(crate) expires_at: SyncUnsafeCell<u64>,
Expand All @@ -63,7 +63,7 @@ unsafe impl Send for TaskRef where &'static TaskHeader: Send {}
unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {}

impl TaskRef {
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
pub(crate) fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
Self {
ptr: NonNull::from(task).cast(),
}
Expand Down Expand Up @@ -103,8 +103,8 @@ impl TaskRef {
// This makes it safe to cast between TaskHeader and TaskStorage pointers.
#[repr(C)]
pub struct TaskStorage<F: Future + 'static> {
raw: TaskHeader,
future: UninitCell<F>, // Valid if STATE_SPAWNED
pub(crate) raw: TaskHeader,
pub(crate) future: UninitCell<F>, // Valid if STATE_SPAWNED
}

impl<F: Future + 'static> TaskStorage<F> {
Expand Down Expand Up @@ -150,7 +150,7 @@ impl<F: Future + 'static> TaskStorage<F> {
}
}

unsafe fn poll(p: TaskRef) {
pub(crate) unsafe fn poll(p: TaskRef) {
let this = &*(p.as_ptr() as *const TaskStorage<F>);

let future = Pin::new_unchecked(this.future.as_mut());
Expand Down
67 changes: 67 additions & 0 deletions embassy-executor/src/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use core::task::Poll;

use super::raw;

#[cfg(feature = "alloc")]
extern crate alloc;

/// Token to spawn a newly-created task in an executor.
///
/// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned
Expand Down Expand Up @@ -59,6 +62,10 @@ pub enum SpawnError {
/// running at a time. You may allow multiple instances to run in parallel with
/// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage.
Busy,

#[cfg(feature = "alloc")]
/// Out of memory.
OOM,
}

/// Handle to spawn tasks into an executor.
Expand Down Expand Up @@ -115,6 +122,36 @@ impl Spawner {
}
}

#[cfg(feature = "alloc")]
/// Spawn a dynamically allocated task into the executor.
/// LIMITATIONS AND WARNINGS:
/// 1. `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
/// is an `async fn`, NOT a hand-written `Future`.
/// 2. `my_async_fn` must never return (runs forever).
pub fn spawn_fut<F: core::future::Future + 'static, FutFn: FnOnce() -> F>(&self, future: FutFn) -> Result<(), SpawnError> {
use alloc::alloc::{alloc, Layout};
use crate::raw::{TaskRef, TaskStorage};

let task_storage = unsafe {
let layout = Layout::new::<TaskStorage<F>>();
let task_storage = alloc(layout) as *mut TaskStorage<F>;
if task_storage.is_null() {
return Err(SpawnError::OOM);
}
task_storage.write(TaskStorage::<F>::new());
let task_storage = &mut *task_storage;
task_storage.raw.state.spawn();
task_storage.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
task_storage.future.write_in_place(future);
task_storage
};

let task_ref = TaskRef::new(task_storage);
let token = unsafe { SpawnToken::<F>::new(task_ref) };
self.spawn(token).unwrap();
Ok(())
}

// Used by the `embassy_executor_macros::main!` macro to throw an error when spawn
// fails. This is here to allow conditional use of `defmt::unwrap!`
// without introducing a `defmt` feature in the `embassy_executor_macros` package,
Expand Down Expand Up @@ -186,6 +223,36 @@ impl SendSpawner {
}
}

#[cfg(feature = "alloc")]
/// Spawn a dynamically allocated task into the executor.
/// LIMITATIONS AND WARNINGS:
/// 1. `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
/// is an `async fn`, NOT a hand-written `Future`.
/// 2. `my_async_fn` must never return (runs forever).
pub fn spawn_fut<F: core::future::Future + 'static, FutFn: (FnOnce() -> F) + Send>(&self, future: FutFn) -> Result<(), SpawnError> {
use alloc::alloc::{alloc, Layout};
use crate::raw::{TaskRef, TaskStorage};

let task_storage = unsafe {
let layout = Layout::new::<TaskStorage<F>>();
let task_storage = alloc(layout) as *mut TaskStorage<F>;
if task_storage.is_null() {
return Err(SpawnError::OOM);
}
task_storage.write(TaskStorage::<F>::new());
let task_storage = &mut *task_storage;
task_storage.raw.state.spawn();
task_storage.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
task_storage.future.write_in_place(future);
task_storage
};

let task_ref = TaskRef::new(task_storage);
let token = unsafe { SpawnToken::<FutFn>::new(task_ref) };
self.spawn(token).unwrap();
Ok(())
}

/// Spawn a task into an executor, panicking on failure.
///
/// # Panics
Expand Down