Improve executor naming. Add docs.
This commit is contained in:
parent
e56c6166dc
commit
297de612e5
@ -115,12 +115,12 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let result = quote! {
|
||||
#(#attrs)*
|
||||
#visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> {
|
||||
use #embassy_path::executor::raw::Task;
|
||||
use #embassy_path::executor::raw::TaskStorage;
|
||||
#task_fn
|
||||
type F = #impl_ty;
|
||||
const NEW_TASK: Task<F> = Task::new();
|
||||
static POOL: [Task<F>; #pool_size] = [NEW_TASK; #pool_size];
|
||||
unsafe { Task::spawn_pool(&POOL, move || task(#arg_names)) }
|
||||
const NEW_TASK: TaskStorage<F> = TaskStorage::new();
|
||||
static POOL: [TaskStorage<F>; #pool_size] = [NEW_TASK; #pool_size];
|
||||
unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) }
|
||||
}
|
||||
};
|
||||
result.into()
|
||||
|
@ -4,12 +4,23 @@ use core::ptr;
|
||||
use super::{raw, Spawner};
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
|
||||
/// Thread mode executor, using WFE/SEV.
|
||||
///
|
||||
/// This is the simplest and most common kind of executor. It runs on
|
||||
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
|
||||
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
|
||||
/// is executed, to make the `WFE` exit from sleep and poll the task.
|
||||
///
|
||||
/// This executor allows for ultra low power consumption for chips where `WFE`
|
||||
/// triggers low-power sleep without extra steps. If your chip requires extra steps,
|
||||
/// you may use [`raw::Executor`] directly to program custom behavior.
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(|_| cortex_m::asm::sev(), ptr::null_mut()),
|
||||
@ -17,14 +28,29 @@ impl Executor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the executor.
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [Forever](crate::util::Forever) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(unsafe { self.inner.spawner() });
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.run_queued() };
|
||||
unsafe { self.inner.poll() };
|
||||
cortex_m::asm::wfe();
|
||||
}
|
||||
}
|
||||
@ -41,6 +67,27 @@ fn pend_by_number(n: u16) {
|
||||
cortex_m::peripheral::NVIC::pend(N(n))
|
||||
}
|
||||
|
||||
/// Interrupt mode executor.
|
||||
///
|
||||
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
|
||||
/// to poll tasks, and when a task is woken the interrupt is pended from software.
|
||||
///
|
||||
/// This allows running async tasks at a priority higher than thread mode. One
|
||||
/// use case is to leave thread mode free for non-async tasks. Another use case is
|
||||
/// to run multiple executors: one in thread mode for low priority tasks and another in
|
||||
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
|
||||
/// priority ones.
|
||||
///
|
||||
/// It is even possible to run multiple interrupt mode executors at different priorities,
|
||||
/// by assigning different priorities to the interrupts. For an example on how to do this,
|
||||
/// See the 'multiprio' example for 'embassy-nrf'.
|
||||
///
|
||||
/// To use it, you have to pick an interrupt that won't be used by the hardware.
|
||||
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
|
||||
/// If this is not the case, you may use an interrupt from any unused peripheral.
|
||||
///
|
||||
/// It is somewhat more complex to use, it's recommended to use the thread-mode
|
||||
/// [`Executor`] instead, if it works for your use case.
|
||||
pub struct InterruptExecutor<I: Interrupt> {
|
||||
irq: I,
|
||||
inner: raw::Executor,
|
||||
@ -48,6 +95,7 @@ pub struct InterruptExecutor<I: Interrupt> {
|
||||
}
|
||||
|
||||
impl<I: Interrupt> InterruptExecutor<I> {
|
||||
/// Create a new Executor.
|
||||
pub fn new(irq: I) -> Self {
|
||||
let ctx = irq.number() as *mut ();
|
||||
Self {
|
||||
@ -59,16 +107,29 @@ impl<I: Interrupt> InterruptExecutor<I> {
|
||||
|
||||
/// Start the executor.
|
||||
///
|
||||
/// `init` is called in the interrupt context, then the interrupt is
|
||||
/// configured to run the executor.
|
||||
/// The `init` closure is called from interrupt mode, with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the interrupt is configured so that the executor starts running the tasks.
|
||||
/// Once the executor is started, `start` returns.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [Forever](crate::util::Forever) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
pub fn start(&'static mut self, init: impl FnOnce(Spawner) + Send) {
|
||||
self.irq.disable();
|
||||
|
||||
init(unsafe { self.inner.spawner() });
|
||||
init(self.inner.spawner());
|
||||
|
||||
self.irq.set_handler(|ctx| unsafe {
|
||||
let executor = &*(ctx as *const raw::Executor);
|
||||
executor.run_queued();
|
||||
executor.poll();
|
||||
});
|
||||
self.irq.set_handler_context(&self.inner as *const _ as _);
|
||||
self.irq.enable();
|
||||
|
@ -3,6 +3,7 @@ use std::sync::{Condvar, Mutex};
|
||||
|
||||
use super::{raw, Spawner};
|
||||
|
||||
/// Single-threaded std-based executor.
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
@ -10,6 +11,7 @@ pub struct Executor {
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
let signaler = &*Box::leak(Box::new(Signaler::new()));
|
||||
Self {
|
||||
@ -25,14 +27,29 @@ impl Executor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the executor.
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [Forever](crate::util::Forever) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(unsafe { self.inner.spawner() });
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.run_queued() };
|
||||
unsafe { self.inner.poll() };
|
||||
self.signaler.wait()
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
//! Async task executor.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[cfg_attr(feature = "std", path = "arch/std.rs")]
|
||||
#[cfg_attr(not(feature = "std"), path = "arch/arm.rs")]
|
||||
mod arch;
|
||||
|
@ -1,3 +1,12 @@
|
||||
//! Raw executor.
|
||||
//!
|
||||
//! This module exposes "raw" Executor and Task structs for more low level control.
|
||||
//!
|
||||
//! ## WARNING: here be dragons!
|
||||
//!
|
||||
//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe
|
||||
//! executor wrappers in [`crate::executor`] and the [`crate::task`] macro, which are fully safe.
|
||||
|
||||
mod run_queue;
|
||||
#[cfg(feature = "time")]
|
||||
mod timer_queue;
|
||||
@ -30,6 +39,10 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
|
||||
#[cfg(feature = "time")]
|
||||
pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2;
|
||||
|
||||
/// Raw task header for use in task pointers.
|
||||
///
|
||||
/// This is an opaque struct, used for raw pointers to tasks, for use
|
||||
/// with funtions like [`wake_task`] and [`task_from_waker`].
|
||||
pub struct TaskHeader {
|
||||
pub(crate) state: AtomicU32,
|
||||
pub(crate) run_queue_item: RunQueueItem,
|
||||
@ -85,15 +98,29 @@ impl TaskHeader {
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage in which a task can be spawned.
|
||||
///
|
||||
/// This struct holds the necessary memory to spawn one task whose future is `F`.
|
||||
/// At a given time, the `Task` may be in spawned or not-spawned state. You may spawn it
|
||||
/// with [`Task::spawn()`], which will fail if it is already spawned.
|
||||
///
|
||||
/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished
|
||||
/// running. Hence the relevant methods require `&'static self`. It may be reused, however.
|
||||
///
|
||||
/// Internally, the [embassy::task](crate::task) macro allocates an array of `TaskStorage`s
|
||||
/// in a `static`. The most common reason to use the raw `Task` is to have control of where
|
||||
/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc.
|
||||
|
||||
// repr(C) is needed to guarantee that the Task is located at offset 0
|
||||
// This makes it safe to cast between Task and Task pointers.
|
||||
#[repr(C)]
|
||||
pub struct Task<F: Future + 'static> {
|
||||
pub struct TaskStorage<F: Future + 'static> {
|
||||
raw: TaskHeader,
|
||||
future: UninitCell<F>, // Valid if STATE_SPAWNED
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> Task<F> {
|
||||
impl<F: Future + 'static> TaskStorage<F> {
|
||||
/// Create a new Task, in not-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
raw: TaskHeader::new(),
|
||||
@ -101,6 +128,12 @@ impl<F: Future + 'static> Task<F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to spawn a task in a pool.
|
||||
///
|
||||
/// See [`Self::spawn()`] for details.
|
||||
///
|
||||
/// This will loop over the pool and spawn the task in the first storage that
|
||||
/// is currently free. If none is free,
|
||||
pub fn spawn_pool(pool: &'static [Self], future: impl FnOnce() -> F) -> SpawnToken<F> {
|
||||
for task in pool {
|
||||
if task.spawn_allocate() {
|
||||
@ -111,6 +144,19 @@ impl<F: Future + 'static> Task<F> {
|
||||
SpawnToken::new_failed()
|
||||
}
|
||||
|
||||
/// Try to spawn the task.
|
||||
///
|
||||
/// The `future` closure constructs the future. It's only called if spawning is
|
||||
/// actually possible. It is a closure instead of a simple `future: F` param to ensure
|
||||
/// the future is constructed in-place, avoiding a temporary copy in the stack thanks to
|
||||
/// NRVO optimizations.
|
||||
///
|
||||
/// This function will fail if the task is already spawned and has not finished running.
|
||||
/// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will
|
||||
/// cause [`Executor::spawn()`] to return the error.
|
||||
///
|
||||
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it
|
||||
/// on a different executor.
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<F> {
|
||||
if self.spawn_allocate() {
|
||||
unsafe { self.spawn_initialize(future) }
|
||||
@ -136,7 +182,7 @@ impl<F: Future + 'static> Task<F> {
|
||||
}
|
||||
|
||||
unsafe fn poll(p: NonNull<TaskHeader>) {
|
||||
let this = &*(p.as_ptr() as *const Task<F>);
|
||||
let this = &*(p.as_ptr() as *const TaskStorage<F>);
|
||||
|
||||
let future = Pin::new_unchecked(this.future.as_mut());
|
||||
let waker = waker::from_task(p);
|
||||
@ -155,8 +201,27 @@ impl<F: Future + 'static> Task<F> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<F: Future + 'static> Sync for Task<F> {}
|
||||
unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {}
|
||||
|
||||
/// Raw executor.
|
||||
///
|
||||
/// This is the core of the Embassy executor. It is low-level, requiring manual
|
||||
/// handling of wakeups and task polling. If you can, prefer using one of the
|
||||
/// higher level executors in [`crate::executor`].
|
||||
///
|
||||
/// The raw executor leaves it up to you to handle wakeups and scheduling:
|
||||
///
|
||||
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
|
||||
/// that "want to run").
|
||||
/// - You must supply a `signal_fn`. The executor will call it to notify you it has work
|
||||
/// to do. You must arrange for `poll()` to be called as soon as possible.
|
||||
///
|
||||
/// `signal_fn` can be called from *any* context: any thread, any interrupt priority
|
||||
/// level, etc. It may be called synchronously from any `Executor` method call as well.
|
||||
/// You must deal with this correctly.
|
||||
///
|
||||
/// In particular, you must NOT call `poll` directly from `signal_fn`, as this violates
|
||||
/// the requirement for `poll` to not be called reentrantly.
|
||||
pub struct Executor {
|
||||
run_queue: RunQueue,
|
||||
signal_fn: fn(*mut ()),
|
||||
@ -169,6 +234,12 @@ pub struct Executor {
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new executor.
|
||||
///
|
||||
/// When the executor has work to do, it will call `signal_fn` with
|
||||
/// `signal_ctx` as argument.
|
||||
///
|
||||
/// See [`Executor`] docs for details on `signal_fn`.
|
||||
pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self {
|
||||
#[cfg(feature = "time")]
|
||||
let alarm = unsafe { unwrap!(driver::allocate_alarm()) };
|
||||
@ -187,23 +258,51 @@ impl Executor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_signal_ctx(&mut self, signal_ctx: *mut ()) {
|
||||
self.signal_ctx = signal_ctx;
|
||||
}
|
||||
|
||||
unsafe fn enqueue(&self, item: *mut TaskHeader) {
|
||||
if self.run_queue.enqueue(item) {
|
||||
/// Enqueue a task in the task queue
|
||||
///
|
||||
/// # Safety
|
||||
/// - `task` must be a valid pointer to a spawned task.
|
||||
/// - `task` must be set up to run in this executor.
|
||||
/// - `task` must NOT be already enqueued (in this executor or another one).
|
||||
unsafe fn enqueue(&self, task: *mut TaskHeader) {
|
||||
if self.run_queue.enqueue(task) {
|
||||
(self.signal_fn)(self.signal_ctx)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) {
|
||||
/// Spawn a task in this executor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `task` must be a valid pointer to an initialized but not-already-spawned task.
|
||||
///
|
||||
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
|
||||
/// In this case, the task's Future must be Send. This is because this is effectively
|
||||
/// sending the task to the executor thread.
|
||||
pub(super) unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) {
|
||||
let task = task.as_ref();
|
||||
task.executor.set(self);
|
||||
self.enqueue(task as *const _ as _);
|
||||
}
|
||||
|
||||
pub unsafe fn run_queued(&'static self) {
|
||||
/// Poll all queued tasks in this executor.
|
||||
///
|
||||
/// This loops over all tasks that are queued to be polled (i.e. they're
|
||||
/// freshly spawned or they've been woken). Other tasks are not polled.
|
||||
///
|
||||
/// You must call `poll` after receiving a call to `signal_fn`. It is OK
|
||||
/// to call `poll` even when not requested by `signal_fn`, but it wastes
|
||||
/// energy.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must NOT call `poll` reentrantly on the same executor.
|
||||
///
|
||||
/// In particular, note that `poll` may call `signal_fn` synchronously. Therefore, you
|
||||
/// must NOT directly call `poll()` from your `signal_fn`. Instead, `signal_fn` has to
|
||||
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
|
||||
/// no `poll()` already running.
|
||||
pub unsafe fn poll(&'static self) {
|
||||
#[cfg(feature = "time")]
|
||||
self.timer_queue.dequeue_expired(Instant::now(), |p| {
|
||||
p.as_ref().enqueue();
|
||||
@ -235,18 +334,26 @@ impl Executor {
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
{
|
||||
// If this is in the past, set_alarm will immediately trigger the alarm,
|
||||
// which will make the wfe immediately return so we do another loop iteration.
|
||||
// If this is already in the past, set_alarm will immediately trigger the alarm.
|
||||
// This will cause `signal_fn` to be called, which will cause `poll()` to be called again,
|
||||
// so we immediately do another poll loop iteration.
|
||||
let next_expiration = self.timer_queue.next_expiration();
|
||||
driver::set_alarm(self.alarm, next_expiration.as_ticks());
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn spawner(&'static self) -> super::Spawner {
|
||||
/// Get a spawner that spawns tasks in this executor.
|
||||
///
|
||||
/// It is OK to call this method multiple times to obtain multiple
|
||||
/// `Spawner`s. You may also copy `Spawner`s.
|
||||
pub fn spawner(&'static self) -> super::Spawner {
|
||||
super::Spawner::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake a task by raw pointer.
|
||||
///
|
||||
/// You can obtain task pointers from `Waker`s using [`task_from_waker`].
|
||||
pub unsafe fn wake_task(task: NonNull<TaskHeader>) {
|
||||
task.as_ref().enqueue();
|
||||
}
|
||||
|
@ -39,13 +39,17 @@ impl RunQueue {
|
||||
}
|
||||
|
||||
/// Enqueues an item. Returns true if the queue was empty.
|
||||
pub(crate) unsafe fn enqueue(&self, item: *mut TaskHeader) -> bool {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `item` must NOT be already enqueued in any queue.
|
||||
pub(crate) unsafe fn enqueue(&self, task: *mut TaskHeader) -> bool {
|
||||
let mut prev = self.head.load(Ordering::Acquire);
|
||||
loop {
|
||||
(*item).run_queue_item.next.store(prev, Ordering::Relaxed);
|
||||
(*task).run_queue_item.next.store(prev, Ordering::Relaxed);
|
||||
match self
|
||||
.head
|
||||
.compare_exchange_weak(prev, item, Ordering::AcqRel, Ordering::Acquire)
|
||||
.compare_exchange_weak(prev, task, Ordering::AcqRel, Ordering::Acquire)
|
||||
{
|
||||
Ok(_) => break,
|
||||
Err(next_prev) => prev = next_prev,
|
||||
@ -55,17 +59,25 @@ impl RunQueue {
|
||||
prev.is_null()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) {
|
||||
let mut task = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
/// Empty the queue, then call `on_task` for each task that was in the queue.
|
||||
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
|
||||
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
|
||||
pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) {
|
||||
// Atomically empty the queue.
|
||||
let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
|
||||
while !task.is_null() {
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = NonNull::new(ptr) {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
let next = (*task).run_queue_item.next.load(Ordering::Relaxed);
|
||||
let next = unsafe { task.as_ref() }
|
||||
.run_queue_item
|
||||
.next
|
||||
.load(Ordering::Relaxed);
|
||||
|
||||
on_task(NonNull::new_unchecked(task));
|
||||
on_task(task);
|
||||
|
||||
task = next
|
||||
ptr = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,17 @@ pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker {
|
||||
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
|
||||
}
|
||||
|
||||
/// Get a task pointer from a waker.
|
||||
///
|
||||
/// This can used as an optimization in wait queues to store task pointers
|
||||
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
|
||||
/// avoid dynamic dispatch.
|
||||
///
|
||||
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the waker is not created by the Embassy executor.
|
||||
pub unsafe fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> {
|
||||
let hack: &WakerHack = mem::transmute(waker);
|
||||
if hack.vtable != &VTABLE {
|
||||
|
@ -4,7 +4,17 @@ use core::ptr::NonNull;
|
||||
|
||||
use super::raw;
|
||||
|
||||
#[must_use = "Calling a task function does nothing on its own. You must pass the returned SpawnToken to Executor::spawn()"]
|
||||
/// Token to spawn a newly-created task in an executor.
|
||||
///
|
||||
/// When calling a task function (like `#[embassy::task] async fn my_task() { ... }`), the returned
|
||||
/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must
|
||||
/// then spawn it into an executor, typically with [`Spawner::spawn()`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way.
|
||||
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
|
||||
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
|
||||
pub struct SpawnToken<F> {
|
||||
raw_task: Option<NonNull<raw::TaskHeader>>,
|
||||
phantom: PhantomData<*mut F>,
|
||||
@ -29,13 +39,19 @@ impl<F> SpawnToken<F> {
|
||||
impl<F> Drop for SpawnToken<F> {
|
||||
fn drop(&mut self) {
|
||||
// TODO deallocate the task instead.
|
||||
panic!("SpawnToken instances may not be dropped. You must pass them to Executor::spawn()")
|
||||
panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()")
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned when spawning a task.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SpawnError {
|
||||
/// Too many instances of this task are already running.
|
||||
///
|
||||
/// By default, a task marked with `#[embassy::task]` can only have one instance
|
||||
/// running at a time. You may allow multiple instances to run in parallel with
|
||||
/// `#[embassy::task(pool_size = 4)]`, at the cost of higher RAM usage.
|
||||
Busy,
|
||||
}
|
||||
|
||||
@ -52,13 +68,16 @@ pub struct Spawner {
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub(crate) unsafe fn new(executor: &'static raw::Executor) -> Self {
|
||||
pub(crate) fn new(executor: &'static raw::Executor) -> Self {
|
||||
Self {
|
||||
executor,
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a task into an executor.
|
||||
///
|
||||
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]).
|
||||
pub fn spawn<F>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> {
|
||||
let task = token.raw_task;
|
||||
mem::forget(token);
|
||||
@ -93,10 +112,11 @@ impl Spawner {
|
||||
|
||||
/// Handle to spawn tasks into an executor from any thread.
|
||||
///
|
||||
/// This Spawner can be used from any thread (it implements Send and Sync, so after any task (Send and non-Send ones), but it can
|
||||
/// only be used in the executor thread (it is not Send itself).
|
||||
/// This Spawner can be used from any thread (it is Send), but it can
|
||||
/// only spawn Send tasks. The reason for this is spawning is effectively
|
||||
/// "sending" the tasks to the executor thread.
|
||||
///
|
||||
/// If you want to spawn tasks from another thread, use [SendSpawner].
|
||||
/// If you want to spawn non-Send tasks, use [Spawner].
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SendSpawner {
|
||||
executor: &'static raw::Executor,
|
||||
@ -106,13 +126,10 @@ pub struct SendSpawner {
|
||||
unsafe impl Send for SendSpawner {}
|
||||
unsafe impl Sync for SendSpawner {}
|
||||
|
||||
/// Handle to spawn tasks to an executor.
|
||||
///
|
||||
/// This Spawner can spawn any task (Send and non-Send ones), but it can
|
||||
/// only be used in the executor thread (it is not Send itself).
|
||||
///
|
||||
/// If you want to spawn tasks from another thread, use [SendSpawner].
|
||||
impl SendSpawner {
|
||||
/// Spawn a task into an executor.
|
||||
///
|
||||
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]).
|
||||
pub fn spawn<F: Send>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> {
|
||||
let header = token.raw_task;
|
||||
mem::forget(token);
|
||||
|
@ -95,12 +95,15 @@ pub trait Driver: Send + Sync + 'static {
|
||||
/// The callback may be called from any context (interrupt or thread mode).
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
|
||||
|
||||
/// Sets an alarm at the given timestamp. When the current timestamp reaches that
|
||||
/// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm
|
||||
/// timestamp, the provided callback funcion will be called.
|
||||
///
|
||||
/// If `timestamp` is already in the past, the alarm callback must be immediately fired.
|
||||
/// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`.
|
||||
///
|
||||
/// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
|
||||
///
|
||||
/// Only one alarm can be active at a time. This overwrites any previously-set alarm if any.
|
||||
/// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any.
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64);
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@
|
||||
//!
|
||||
//! For more details, check the [`driver`] module.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod delay;
|
||||
pub mod driver;
|
||||
mod duration;
|
||||
|
@ -8,7 +8,7 @@ use example_common::*;
|
||||
use core::mem;
|
||||
use cortex_m_rt::entry;
|
||||
|
||||
use embassy::executor::raw::Task;
|
||||
use embassy::executor::raw::TaskStorage;
|
||||
use embassy::executor::Executor;
|
||||
use embassy::time::{Duration, Timer};
|
||||
use embassy::util::Forever;
|
||||
@ -36,8 +36,8 @@ fn main() -> ! {
|
||||
let _p = embassy_nrf::init(Default::default());
|
||||
let executor = EXECUTOR.put(Executor::new());
|
||||
|
||||
let run1_task = Task::new();
|
||||
let run2_task = Task::new();
|
||||
let run1_task = TaskStorage::new();
|
||||
let run2_task = TaskStorage::new();
|
||||
|
||||
// Safety: these variables do live forever if main never returns.
|
||||
let run1_task = unsafe { make_static(&run1_task) };
|
||||
|
Loading…
Reference in New Issue
Block a user