From 7a348aebdecff7d6485ef0de61218d8aa874dacf Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 23 Aug 2024 01:59:20 +0200 Subject: [PATCH] examples: Add asynchronous Rust example --- examples/rust-async/Cargo.lock | Bin 0 -> 21748 bytes examples/rust-async/Cargo.toml | 28 +++++++++++++++++ examples/rust-async/Makefile | 30 +++++++++++++++++++ examples/rust-async/README.md | 21 +++++++++++++ examples/rust-async/src/lib.rs | 53 +++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 examples/rust-async/Cargo.lock create mode 100644 examples/rust-async/Cargo.toml create mode 100644 examples/rust-async/Makefile create mode 100644 examples/rust-async/README.md create mode 100644 examples/rust-async/src/lib.rs diff --git a/examples/rust-async/Cargo.lock b/examples/rust-async/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..f2a3dc3b4479eabc256ae2f72991327fee344608 GIT binary patch literal 21748 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 NQr?hEu#*KF@qcMXg#-Wq literal 0 HcmV?d00001 diff --git a/examples/rust-async/Cargo.toml b/examples/rust-async/Cargo.toml new file mode 100644 index 0000000000..69d57736e0 --- /dev/null +++ b/examples/rust-async/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "rust-async" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[profile.release] +# Setting the panic mode has little effect on the built code (as Rust on RIOT +# supports no unwinding), but setting it allows builds on native without using +# the nightly-only lang_items feature. +panic = "abort" + +[dependencies] +riot-wrappers = { version = "0.9", features = [ "set_panic_handler", "provide_critical_section_1_0", "panic_handler_format" ] } + +embassy-executor-riot = { git = "https://gitlab.com/etonomy/riot-module-examples" } +embassy-executor = "0.5" +embassy-futures = "0.1.1" +static_cell = "2.0.0" + +# While currently this example does not use any RIOT modules implemented in +# Rust, that may change; it is best practice for any RIOT application that has +# its own top-level Rust crate to include rust_riotmodules from inside +# RIOTBASE. +rust_riotmodules = { path = "../../sys/rust_riotmodules/" } diff --git a/examples/rust-async/Makefile b/examples/rust-async/Makefile new file mode 100644 index 0000000000..f782dac844 --- /dev/null +++ b/examples/rust-async/Makefile @@ -0,0 +1,30 @@ +# name of your application +APPLICATION = rust-async + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Some timers for the example +USEMODULE += ztimer_msec + +# Required by the async executor +USEMODULE += core_thread_flags + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_') +APPLICATION_RUST_MODULE = rust_async +BASELIBS += $(APPLICATION_RUST_MODULE).module + +FEATURES_REQUIRED += rust_target + +include $(RIOTBASE)/Makefile.include diff --git a/examples/rust-async/README.md b/examples/rust-async/README.md new file mode 100644 index 0000000000..222c368088 --- /dev/null +++ b/examples/rust-async/README.md @@ -0,0 +1,21 @@ +Rust: Asynchronous programming +============================== + +This is an example of how asynchronous Rust applications can be written on RIOT OS. + +The application starts an [embassy] based executor, +and then spawn several tasks on the main thread. +Unlike RIOT OS threads, these tasks can not preempt each other; +they relinquish control of the main thread when performing an asynchronous operation that would block, +and are paused by anything that preempts the main thread. + +Behind the scenes, the Rust compiler turns the asynchronous methods into state machines, +and the executor polls the state machines to make progress whenever an event wakes them up. + +Upsides of this style of programming are that there is less need for synchronization +(for individual tasks or their parts can rely on the work they do synchronously not to be executed concurrently with other tasks if so configured), +and that they can share a single stack space. +State that is persisted across await points is not stored on the stack, +but in adequately sized static memory for each task that can run. + +[embassy]: https://embassy.dev/ diff --git a/examples/rust-async/src/lib.rs b/examples/rust-async/src/lib.rs new file mode 100644 index 0000000000..41a9f409bc --- /dev/null +++ b/examples/rust-async/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2020 Christian Amsüss +// +// This file is subject to the terms and conditions of the GNU Lesser +// General Public License v2.1. See the file LICENSE in the top level +// directory for more details. +#![no_std] + +use riot_wrappers::riot_main; +use riot_wrappers::println; +use riot_wrappers::ztimer::{Clock, Ticks}; + +use static_cell::StaticCell; + +extern crate rust_riotmodules; + +riot_main!(main); + +fn main() { + // Create an executor for running async functions + static EXECUTOR: StaticCell = StaticCell::new(); + let executor: &'static mut _ = EXECUTOR.init(embassy_executor_riot::Executor::new()); + executor.run(|spawner| { + spawner.spawn(amain(spawner)).expect("Starting task for the first time"); + }) +} + +#[embassy_executor::task] +async fn amain(spawner: embassy_executor::Spawner) { + println!("Running asynchronously:"); + + spawner.spawn(fast_messages("A")).expect("Starting task for the first time"); + spawner.spawn(fast_messages("B")).expect("Task is configured to allow two instances"); + + let msec = Clock::msec(); + + msec.sleep_async(Ticks(2500)).await; + + println!("Slept 2.5 seconds in 'amain' task, that should be enough."); +} + +#[embassy_executor::task(pool_size = 2)] +async fn fast_messages(taskname: &'static str) { + println!("Task {taskname} is running:"); + + let msec = Clock::msec(); + + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} ticked"); + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} tocked"); + msec.sleep_async(Ticks(1000)).await; + println!("Task {taskname} is done."); +}