Skip to content
Merged
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
4 changes: 4 additions & 0 deletions book/en/src/by-example/software_tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ $ cargo xtask qemu --verbose --example spawn_arguments
{{#include ../../../../ci/expected/lm3s6965/spawn_arguments.run}}
```

## Divergent tasks

A task can have one of two signatures: `async fn({name}::Context, ..)` or `async fn({name}::Context, ..) -> !`. The latter defines a *divergent* task — one that never returns. The key advantage of divergent tasks is that they receive a `'static` context, and `local` resources have `'static` lifetime. Additionally, using this signature makes the task’s intent explicit, clearly distinguishing between short-lived tasks and those that run indefinitely. Be mindful not to starve other tasks at the same priority level by ensuring you yield control with `.await`.

## Priority zero tasks

In RTIC tasks run preemptively to each other, with priority zero (0) the lowest priority. You can use priority zero tasks for background work, without any strict real-time requirements.
Expand Down
8 changes: 4 additions & 4 deletions book/en/src/by-example/tips/static_lifetimes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 'static super-powers

In `#[init]` and `#[idle]` `local` resources have `'static` lifetime.
In `#[init]`, `#[idle]` and divergent software tasks `local` resources have `'static` lifetime.

Useful when pre-allocating and/or splitting resources between tasks, drivers or some other object. This comes in handy when drivers, such as USB drivers, need to allocate memory and when using splittable data structures such as [`heapless::spsc::Queue`].

Expand All @@ -9,15 +9,15 @@ In the following example two different tasks share a [`heapless::spsc::Queue`] f
[`heapless::spsc::Queue`]: https://docs.rs/heapless/0.7.5/heapless/spsc/struct.Queue.html

```rust,noplayground
{{#include ../../../../../examples/lm3s6965/examples/static.rs}}
{{#include ../../../../../examples/lm3s6965/examples/static-resources-in-init.rs}}
```

Running this program produces the expected output.

```console
$ cargo xtask qemu --verbose --example static
$ cargo xtask qemu --verbose --example static-resources-in-init
```

```console
{{#include ../../../../../ci/expected/lm3s6965/static.run}}
{{#include ../../../../../ci/expected/lm3s6965/static-resources-in-init.run}}
```
4 changes: 4 additions & 0 deletions ci/expected/lm3s6965/static-recources-in-divergent.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
received message: 0
received message: 1
received message: 2
received message: 3
67 changes: 67 additions & 0 deletions examples/lm3s6965/examples/static-recources-in-divergent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! examples/static-resources-in-divergent.rs

#![no_main]
#![no_std]
#![deny(warnings)]
#![deny(unsafe_code)]
#![deny(missing_docs)]

use panic_semihosting as _;
use rtic_monotonics::systick::prelude::*;
systick_monotonic!(Mono, 100);

#[rtic::app(device = lm3s6965, dispatchers = [UART0])]
mod app {
use super::*;

use cortex_m_semihosting::{debug, hprintln};
use rtic_sync::channel::{Channel, Receiver};

#[shared]
struct Shared {}

#[local]
struct Local {}

#[init]
fn init(cx: init::Context) -> (Shared, Local) {
Mono::start(cx.core.SYST, 12_000_000);

divergent::spawn().ok();

(Shared {}, Local {})
}

#[task(local = [q: Channel<u32, 5> = Channel::new()], priority = 1)]
async fn divergent(cx: divergent::Context) -> ! {
// `q` has `'static` lifetime. You can put references to it in `static` variables,
// structs with references to `q` do not need a generic lifetime parameter, etc.

let (mut tx, rx) = cx.local.q.split();
let mut state = 0;

bar::spawn(rx).unwrap();

loop {
tx.send(state).await.unwrap();
state += 1;
Mono::delay(100.millis()).await;
}
}

#[task(priority = 1)]
async fn bar(_cx: bar::Context, mut rx: Receiver<'static, u32, 5>) -> ! {
loop {
// Lock-free access to the same underlying queue!
if let Some(data) = rx.recv().await.ok() {
hprintln!("received message: {}", data);

if data == 3 {
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
} else {
Mono::delay(100.millis()).await;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! examples/static.rs
//! examples/static-resources-in-init.rs

#![no_main]
#![no_std]
Expand Down
1 change: 1 addition & 0 deletions rtic-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
## [Unreleased]

- Adapt `slic` backends to new version with `mecall`
- Allow software tasks to be diverging (return `!`) and give them `'static` context.

## [v2.1.1] - 2024-12-06

Expand Down
6 changes: 4 additions & 2 deletions rtic-macros/src/codegen/software_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 {
mod_app.push(constructor);
}

if !&task.is_extern {
if !task.is_extern {
let context = &task.context;
let attrs = &task.attrs;
let cfgs = &task.cfgs;
let stmts = &task.stmts;
let inputs = &task.inputs;
let lifetime = if task.is_bottom { quote!('static) } else { quote!('a) };
let generics = if task.is_bottom { quote!() } else { quote!(<'a>) };

user_tasks.push(quote!(
#(#attrs)*
#(#cfgs)*
#[allow(non_snake_case)]
async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) {
async fn #name #generics(#context: #name::Context<#lifetime> #(,#inputs)*) {
use rtic::Mutex as _;
use rtic::mutex::prelude::*;

Expand Down
3 changes: 3 additions & 0 deletions rtic-macros/src/syntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ pub struct SoftwareTask {

/// The task is declared externally
pub is_extern: bool,

/// The task will never return `Poll::Ready`
pub is_bottom: bool,
}

/// Software task metadata
Expand Down
13 changes: 9 additions & 4 deletions rtic-macros/src/syntax/parse/software_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::syntax::{

impl SoftwareTask {
pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result<Self> {
let is_bottom = util::type_is_bottom(&item.sig.output);
let valid_signature = util::check_fn_signature(&item, true)
&& util::type_is_unit(&item.sig.output)
&& (util::type_is_unit(&item.sig.output) || is_bottom)
&& item.sig.asyncness.is_some();

let span = item.sig.ident.span();
Expand All @@ -28,13 +29,14 @@ impl SoftwareTask {
inputs,
stmts: item.block.stmts,
is_extern: false,
is_bottom,
});
}
}

Err(parse::Error::new(
span,
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
format!("this task handler must have type signature `async fn({name}::Context, ..)` or `async fn({name}::Context, ..) -> !`"),
))
}
}
Expand All @@ -44,8 +46,10 @@ impl SoftwareTask {
args: SoftwareTaskArgs,
item: ForeignItemFn,
) -> parse::Result<Self> {
let is_bottom = util::type_is_bottom(&item.sig.output);

let valid_signature = util::check_foreign_fn_signature(&item, true)
&& util::type_is_unit(&item.sig.output)
&& (util::type_is_unit(&item.sig.output) || is_bottom)
&& item.sig.asyncness.is_some();

let span = item.sig.ident.span();
Expand All @@ -64,13 +68,14 @@ impl SoftwareTask {
inputs,
stmts: Vec::<Stmt>::new(),
is_extern: true,
is_bottom,
});
}
}

Err(parse::Error::new(
span,
format!("this task handler must have type signature `async fn({name}::Context, ..)`"),
format!("this task handler must have type signature `async fn({name}::Context, ..)` or `async fn({name}::Context, ..) -> !`"),
))
}
}
9 changes: 0 additions & 9 deletions rtic-macros/ui/task-divergent.rs

This file was deleted.

5 changes: 0 additions & 5 deletions rtic-macros/ui/task-divergent.stderr

This file was deleted.

2 changes: 1 addition & 1 deletion rtic-macros/ui/task-no-context.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: this task handler must have type signature `async fn(foo::Context, ..)`
error: this task handler must have type signature `async fn(foo::Context, ..)` or `async fn(foo::Context, ..) -> !`
--> ui/task-no-context.rs:6:14
|
6 | async fn foo() {}
Expand Down
2 changes: 1 addition & 1 deletion rtic-macros/ui/task-pub.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: this task handler must have type signature `async fn(foo::Context, ..)`
error: this task handler must have type signature `async fn(foo::Context, ..)` or `async fn(foo::Context, ..) -> !`
--> ui/task-pub.rs:6:18
|
6 | pub async fn foo(_: foo::Context) {}
Expand Down
2 changes: 1 addition & 1 deletion rtic-macros/ui/task-unsafe.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: this task handler must have type signature `async fn(foo::Context, ..)`
error: this task handler must have type signature `async fn(foo::Context, ..)` or `async fn(foo::Context, ..) -> !`
--> ui/task-unsafe.rs:6:21
|
6 | async unsafe fn foo(_: foo::Context) {}
Expand Down
2 changes: 1 addition & 1 deletion rtic-macros/ui/task-zero-prio.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: this task handler must have type signature `async fn(foo::Context, ..)`
error: this task handler must have type signature `async fn(foo::Context, ..)` or `async fn(foo::Context, ..) -> !`
--> ui/task-zero-prio.rs:15:8
|
15 | fn foo(_: foo::Context) {}
Expand Down
1 change: 1 addition & 0 deletions rtic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Example:
### Changed

- Updated esp32c3 dependency to v0.27.0
- Allow software tasks to be diverging (return `!`) and give them `'static` context.

### Added

Expand Down