Struct lilos::exec::Notify

source ·
pub struct Notify { /* private fields */ }
Expand description

A lightweight task notification scheme that can be used to safely route events from interrupt handlers to task code.

This is the lowest level inter-task communication type in lilos.

Any number of tasks can subscribe to a Notify. When notify is called on it, all those tasks will be awoken (i.e. their Waker will be triggered so that they become eligible for polling), and their subscription is atomically ended.

A Notify is very small (the size of a pointer), so feel free to create as many as you like.

It is safe to call notify from an ISR, so this is the usual method by which interrupt handlers inform task code of events. Normally a Notify used in this way is stored in a static:

static EVENT: Notify = Notify::new();

You can use that style of static Notify to sleep waiting for interrupt conditions in async code. Here’s an example for a made-up but typical UART driver:

/// Event signal for waking task(s) when data arrives.
static RX_NOT_EMPTY: Notify = Notify::new();

/// UART interrupt handler.
#[interrupt]
fn UART() {
    let uart = get_uart_peripheral_somehow();

    let control = uart.control.read();
    let status = uart.status.read();

    if control.rx_irq_enabled() && status.rx_not_empty() {
        // Shut off the interrupt source to keep this from reoccurring.
        uart.control.modify(|_, w| w.rx_irq_enabled().clear());
        // Wake up the task that requested this.
        RX_NOT_EMPTY.notify();
    }
}

async fn uart_recv(uart: &Uart) -> u8 {
    // Enable the rx data interrupt so we get notified.
    uart.control.modify(|_, w| w.rx_irq_enabled().set());
    // Listen for data, using a predicate to filter out spurious wakes.
    RX_NOT_EMPTY.until(|| uart.status.read().rx_not_empty());

    UART.data.read()
}

Waker coalescing

A Notify collects any number of task Wakers into a fixed-size structure without heap allocation. It does this by coalescing the Wakers such that they may become imprecise: firing the waker for task N may also spuriously wake task M. (Implementation-wise, this is a matter of collecting a wake bits mask from the wakers using secret knowledge.)

While this is often not the ideal strategy, it has the advantage that it can be built up cheaply and torn down atomically from interrupt context. (Contrast with e.g. a list of waiting tasks, which is more precise but harder to get right and more expensive at runtime.)

Implementations§

source§

impl Notify

source

pub const fn new() -> Self

Creates a new Notify with no tasks waiting.

source

pub fn subscribe(&self, waker: &Waker)

Adds the Waker to the set of waiters.

source

pub fn notify(&self)

Wakes tasks, at least all those whose waiters have been passed to subscribe since the last notify, possibly more.

source

pub fn until<'a, 'b, T: TestResult>( &'a self, cond: impl FnMut() -> T + 'b ) -> impl Future<Output = T::Output> + 'awhere 'b: 'a,

Repeatedly calls cond, completing when it passes. In between calls, subscribes to self, so that the task will wake less often and leave CPU available for other things.

This is appropriate if you know that any change to cond’s result will be preceded by some task calling self.notify().

Cancellation

Cancel safety: Strict.

Dropping this future will drop cond, and may leave the current task subscribed to self (meaning one potential spurious wakeup in the future is possible).

source

pub fn until_racy<'a, 'b, T: TestResult>( &'a self, cond: impl FnMut() -> T + 'b ) -> impl Future<Output = T::Output> + 'awhere 'b: 'a,

Subscribes to notify and then calls cond, completing if it returns true. Otherwise, waits and tries again. This is very similar to until, and is slightly more expensive, but in exchange it is correct if the condition may be set asynchronously (i.e. you are running the OS with preemption enabled).

Cancellation

Cancel safety: Strict.

Dropping this future will drop cond, and will leave the current task subscribed to self (meaning one potential spurious wakeup in the future is possible).

source

pub fn until_next(&self) -> impl Future<Output = ()> + '_

Subscribes to notify and blocks until the task is awoken. This may produces spurious wakeups, and is appropriate only when you’re checking some condition separately. Otherwise, use until.

Cancellation

Cancel safety: Strict.

Dropping this future will leave the current task subscribed to self (meaning one potential spurious wakeup in the future is possible).

Trait Implementations§

source§

impl Debug for Notify

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Notify

source§

fn default() -> Notify

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.