Skip to main content

Crate embassy_supervisor

Crate embassy_supervisor 

Source
Expand description

§embassy-supervisor — a task-lifecycle supervisor for embassy

Application- and HAL-agnostic primitives for orchestrating a set of embassy tasks: bringing them up in dependency order, tearing them down in reverse, scaling an elastic worker pool with load, and starting/stopping/pausing/ resuming individual tasks at runtime while keeping the dependency graph consistent.

§The model

  • The graph is declared once with the supervisor_graph! macro: each managed task becomes a TaskNode static, and the macro bundles the node slots, dependency table, and a topological order computed at compile time (a dependency cycle is a compile error) into a single Graph (GRAPH).
  • Supervisor::new takes &GRAPH (no work, no failure) and uses the order to bring tasks up in dependency order (Supervisor::start) and tear them down in reverse (Supervisor::teardown).
  • Each node carries a TaskHandle of per-node atomic flags and single-consumer Signals. Every node is single-instance — no counts, no fan-out. See TaskHandle.

§Three lifecycles, distinguished by Mode

  • Mode::Terminate — the task exits its loop on shutdown and is respawned on the next bring-up. Stateless services (a network listener, a logger).
  • Mode::Pause — the task acks the shutdown then parks on wait_resume(); it is resumed in place, never respawned. Tasks that retain a resource across the pause (an open peripheral handle, a socket).
  • Mode::OnDemand — like Terminate, but not started at boot and not auto-respawned; the supervisor brings it up and down at runtime to scale an elastic worker pool (ElasticPool) with load.

§What the supervisor does not do

  • It does not model any power-state transition (sleep/wake): it reacts to “teardown” and “bring-up” requests; the application drives them.
  • It does not allocate, and does no work at construction: the topological sort runs at compile time (see the supervisor_graph! macro).
  • It does not observe task internals. Tasks self-report their drop state via ack_dropped(); a task that fails to ack within a timeout panics the supervisor with the offending node’s name.

§Cargo features

Build with default-features = false for a minimal core that only does dependency-ordered bring-up/teardown (drops the control plane and pools, trimming flash and a couple of statics).

§Example

supervisor_graph! declares the whole graph once — it generates the node statics and a single Graph value GRAPH bundling the node slots, dep table, and compile-time topological order (a dependency cycle is a compile error), which Supervisor::new consumes.

use embassy_executor::Spawner;
use embassy_supervisor::{supervisor_graph, Supervisor, wait_control};

// `app` depends on `net`; each `spawn:` names a task fn spawned with the node.
supervisor_graph! {
    node NET = Terminate, deps: [], spawn: net_task;
    node APP = Terminate, deps: [NET], spawn: app_task;
}

#[embassy_executor::task]
async fn supervisor_task(spawner: Spawner) {
    let sup = Supervisor::new(&GRAPH);
    sup.start(spawner).expect("initial spawn"); // brings up `net`, then `app`
    loop {
        // Apply runtime start/stop/pause/resume requests in dependency order.
        let cmd = wait_control().await;
        sup.apply_control(cmd, spawner).await;
    }
    // With the `pool` feature you'd instead drive scaling and control together:
    // `select(sup.run_pools(spawner), wait_control())` (see the `firmware` crate).
}

The firmware crate in the repository is a complete working example (USB-net, an HTTP control plane, an elastic pool, and OTA).

Docs:

Macros§

supervisor_graph
Declare a supervised task graph and compute its topological order at compile time (single source of nodes, deps, pool, and order). See the embassy-supervisor-macros crate for the surface syntax. Declare a supervised task graph; see the crate docs for the surface syntax.

Structs§

ControlCommand
A runtime control request: drive node (and, per the dependency graph and pool membership, the nodes it implies) in the op direction.
DeferredShrink
Grow immediately (stay responsive), but shrink only after the idle surplus has persisted for cooldown — damps grow→shrink→grow flapping. Holds its pending shrink deadline in a Cell<Option<Instant>> (interior mutability under &self, since the pool is a static); None = no shrink pending.
ElasticPool
An elastic pool of single-instance nodes scaled by policy P.
Graph
The compile-time task graph produced by supervisor_graph!: the node slots, the dependency-index table, the topological order, and the elastic pools — the single value Supervisor::new consumes. The macro emits one pub static GRAPH of this type. The fields are public so the application can read them directly (e.g. a status endpoint iterating GRAPH.nodes / GRAPH.deps).
PoolStats
Aggregate state of a pool, handed to the policy.
Supervisor
Orchestrates a set of managed tasks across spawn / teardown / bring-up.
TaskHandle
Coordination state for one task. Embedded inside TaskNode.
TaskNode
A node in the supervisor’s task graph.

Enums§

ControlOp
Which way to drive a node. Higher-level verbs fold onto these two: start/resumeActivate, stop/pauseDeactivate. The concrete mechanism (respawn vs resume vs leave-to-pool) is then chosen per node Mode by the supervisor when it applies the command (Supervisor::apply_control).
Mode
Lifecycle policy for a managed task: what the task does on shutdown and what the supervisor does to bring it back.
PoolAction
What the supervisor should do for a pool this tick. The async part (start / stop) is applied by the caller, keeping Pool object-safe without futures.
ScaleAction
What a policy wants done this evaluation.

Traits§

Pool
Object-safe, synchronous pool interface so &dyn Pool needs no heap: the policy decides here; the supervisor performs the async start/stop.
ScalingPolicy
Swappable scaling decision. decide is synchronous; stateful policies use interior mutability (the pool is a static, so &self).

Functions§

request_control
Enqueue a control request. Non-blocking; drops if the mailbox is full (4 outstanding), which is harmless for low-frequency manual control. Called by the application’s control surface.
request_scale
Fire the scale-request signal. Called by a task on a busy/idle transition. A no-op when the pool feature is disabled (no pools to re-evaluate).
wait_control
Await the next control request. Selected by the supervisor’s driver loop against pool scaling and any other application wake sources.
wait_scale
Await the next scale request. The supervisor’s driver loop selects this against its other wake sources and runs the scaling policy on each wake.