diff --git a/RELEASE-NOTES.mkdn b/RELEASE-NOTES.mkdn index fd16d65..663da1b 100644 --- a/RELEASE-NOTES.mkdn +++ b/RELEASE-NOTES.mkdn @@ -34,10 +34,11 @@ fix. - Removed operations that were marked as deprecated during the 0.3.x series: `spsc::Push::push`. -- The `handoff` module is no longer available by default. If you want it, - request it by enabling the the `handoff` Cargo feature. (It's incredibly - useful, but has some gotchas, so I wanted to make it a little harder to - reach.) +- The `handoff` module is no longer part of the `lilos` crate. It has been + extracted into a separate `lilos-handoff` crate. If you want it, add that + crate to your project, and change references from `lilos::handoff` to + `lilos_handoff`. (It's incredibly useful, but has some gotchas, so it seemed + inappropriate in the core API.) - The codebase should be ready for the `static_mut_refs` lint in Rust 1.77, which will become a hard error in the eagerly-awaited 2024 edition. diff --git a/build-all.sh b/build-all.sh index e658312..6096053 100755 --- a/build-all.sh +++ b/build-all.sh @@ -2,7 +2,7 @@ set -euo pipefail -DIRS="os testsuite/stm32f4 testsuite/stm32g0 testsuite/stm32f3 examples/*/*" +DIRS="os handoff testsuite/stm32f4 testsuite/stm32g0 testsuite/stm32f3 examples/*/*" for d in $DIRS; do echo "---- building in $d" diff --git a/doc/intro.adoc b/doc/intro.adoc index 564fc73..fd2b94c 100644 --- a/doc/intro.adoc +++ b/doc/intro.adoc @@ -1030,17 +1030,17 @@ which is on by default. === Sending something to another task, but synchronously If you need to send things from task A to task B, and it's okay to make the two -tasks synchronize each time they want to exchange data, then `lilos::handoff` is -your new best friend. Creating a `Handoff` doesn't require any storage, and -exchanging data using a `Handoff` guarantees to only copy your data in memory -once -- unlike `spsc`, which copies data at least twice: once on the way in, -once on the way out. +tasks synchronize each time they want to exchange data, then the +`lilos-handoff` crate is your new best friend. Creating a `Handoff` doesn't +require any storage, and exchanging data using a `Handoff` guarantees to only +copy your data in memory once -- unlike `spsc`, which copies data at least +twice: once on the way in, once on the way out. If you just want the _sender_ to wait while the receiver goes on doing its work, -have a look at the `try_pop` operation on `lilos::handoff::Pop`. +have a look at the `try_pop` operation on `lilos_handoff::Pop`. -TIP: `lilos::handoff` is available if {os} is built with the `handoff` -feature, which is on by default. +TIP: `lilos_handoff` is not part of the core API. Use `cargo add lilos-handoff` +to add it to your project. === Sharing a read-write resource between two or more tasks diff --git a/handoff/.cargo/config.toml b/handoff/.cargo/config.toml new file mode 100644 index 0000000..5d10902 --- /dev/null +++ b/handoff/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "thumbv7em-none-eabihf" diff --git a/handoff/Cargo.lock b/handoff/Cargo.lock new file mode 100644 index 0000000..bcaa3cb --- /dev/null +++ b/handoff/Cargo.lock @@ -0,0 +1,191 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "lilos" +version = "1.0.0-pre.0" +dependencies = [ + "cfg-if", + "cortex-m", + "cortex-m-rt", + "pin-project-lite", +] + +[[package]] +name = "lilos-handoff" +version = "1.0.0-pre.0" +dependencies = [ + "lilos", + "scopeguard", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/handoff/Cargo.toml b/handoff/Cargo.toml new file mode 100644 index 0000000..35e906e --- /dev/null +++ b/handoff/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "lilos-handoff" +version = "1.0.0-pre.0" +authors = ["Cliff L. Biffle "] +edition = "2021" +license = "MPL-2.0" +description = "Synchronous rendezvous structure for lilos" +repository = "https://github.com/cbiffle/lilos/" +keywords = ["async", "embedded", "realtime", "os"] +categories = ["embedded"] +readme = "README.mkdn" +rust-version = "1.69" + +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabihf" + +[dependencies] +lilos = { path = "../os", version = "1.0.0-pre.0" } +scopeguard = { version = "1.2.0", default-features = false } + +[lib] +test = false +bench = false diff --git a/handoff/README.mkdn b/handoff/README.mkdn new file mode 100644 index 0000000..35d5fca --- /dev/null +++ b/handoff/README.mkdn @@ -0,0 +1,11 @@ +# Handoff structure for `lilos` + +This implements a synchronous rendezvous structure, which lets a task pass a +value to another task without extra copies or reserving storage space. + +This used to be part of the core `lilos` API, but was extracted during the +process of finalizing the `lilos` 1.0 version. It is currently separate from +`lilos` because its API is not cancel-safe. + +Despite not being cancel-safe, it's still quite useful. See the module docs for +more details. diff --git a/os/src/handoff.rs b/handoff/src/lib.rs similarity index 93% rename from os/src/handoff.rs rename to handoff/src/lib.rs index b2956cd..c37c93d 100644 --- a/os/src/handoff.rs +++ b/handoff/src/lib.rs @@ -1,5 +1,7 @@ //! Mechanism for handing data from one task to another, minimizing copies. //! +//! This crate provides the `Handoff` abstraction for `lilos`. +//! //! There are two sides to a `Handoff`, the sender and the receiver. When both //! the sender and receiver are ready, a single `T` gets transferred from the //! sender's ownership to the receiver's. In this case, "ready" means that @@ -38,7 +40,7 @@ //! //! If you would like to be able to push data and go on about your business //! without waiting for it to be popped, you want a queue, not a handoff. See -//! the `spsc` module. +//! the `lilos::spsc` module. //! //! Note that none of these types are `Send` or `Sync` -- they are very much not //! thread safe, so they can be freely used across `async` tasks but cannot be @@ -49,10 +51,13 @@ //! //! # Cancel safety //! -//! This module is currently the only part of `lilos` that has non-deprecated -//! API that is not strictly cancel-safe. This is often okay, the way handoffs -//! are used (in my code at least), but please read the docs for -//! [`Pusher::push`] and [`Popper::pop`] carefully or you risk losing data. +//! `Handoff` is not strictly cancel-safe, unlike most of `lilos`. Concretely, +//! dropping a `push` or `pop` future before it resolves can cause the loss of +//! at most one data item. +//! +//! While technically cancel-unsafe, this is usually okay given the way handoffs +//! are used in practice. Please read the docs for [`Pusher::push`] and +//! [`Popper::pop`] carefully or you risk losing data. //! //! If the push and pop ends of the handoff are "long-lived," held by tasks that //! won't be cancelled (such as top-level tasks in `lilos`) and never used in @@ -60,12 +65,14 @@ //! you don't need to worry about that. This is not a property you can check //! with the compiler, though, so again -- be careful. +#![no_std] + use core::cell::Cell; use core::ptr::NonNull; use scopeguard::ScopeGuard; -use crate::exec::Notify; +use lilos::exec::Notify; /// Shared control block for a `Handoff`. See the module docs for more /// information. @@ -148,11 +155,7 @@ impl core::fmt::Debug for State { impl Copy for State {} impl Clone for State { fn clone(&self) -> Self { - match self { - Self::Idle => Self::Idle, - Self::PushWait(p) => Self::PushWait(*p), - Self::PopWait(p) => Self::PopWait(*p), - } + *self // thanks, Copy impl! } } @@ -277,11 +280,13 @@ impl Popper<'_, T> { pub fn try_pop(&mut self) -> Option { match self.0.state.get() { State::PushWait(src_ptr) => { - // Our peer is waiting. - let value = core::mem::replace( - unsafe { &mut *src_ptr.as_ptr() }, - None, - ); + // Our peer is waiting. Take the thingy. + // + // Safety: if we're in this state the source pointer is valid + // and the backing memory is not being used -- since if the peer + // had resumed, it would have knocked us out of this state. + let value = unsafe { &mut *src_ptr.as_ptr() }.take(); + self.0.state.set(State::Idle); self.0.ping.notify(); value diff --git a/msrv-all.sh b/msrv-all.sh index ee4f112..51731be 100755 --- a/msrv-all.sh +++ b/msrv-all.sh @@ -4,7 +4,7 @@ set -euo pipefail jq --version -DIRS="os testsuite/stm32f4 testsuite/stm32g0 testsuite/stm32f3 examples/*/*" +DIRS="os handoff testsuite/stm32f4 testsuite/stm32g0 testsuite/stm32f3 examples/*/*" for d in $DIRS; do pushd $d > /dev/null diff --git a/os/Cargo.toml b/os/Cargo.toml index e5e247d..91cee06 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -20,14 +20,12 @@ default = ["mutex", "spsc", "systick"] mutex = [] spsc = [] systick = [] -handoff = ["scopeguard"] [dependencies] cfg-if = "1.0.0" cortex-m = {version = "0.7.4", features = ["inline-asm"]} cortex-m-rt = "0.7.1" pin-project-lite = "0.2.10" -scopeguard = { version = "1.1.0", default-features = false, optional = true } [lib] test = false diff --git a/os/src/lib.rs b/os/src/lib.rs index f234b6a..63e4721 100644 --- a/os/src/lib.rs +++ b/os/src/lib.rs @@ -61,11 +61,6 @@ //! for single-producer single-consumer inter-task queues. Leaving this feature //! enabled has no cost if you're not actually using `spsc`. //! -//! - `handoff` (**off** by default). Enables access to the -//! [`handoff`][crate::handoff`] module for inexpensive synchronous inter-task -//! rendezvous. `handoff` contains some API that is not strictly cancel-safe, so -//! you need to request it explicitly. -//! //! //! # Composition and dynamic behavior //! @@ -177,5 +172,3 @@ pub mod time; pub mod mutex; #[cfg(feature = "spsc")] pub mod spsc; -#[cfg(feature = "handoff")] -pub mod handoff; diff --git a/testsuite/Cargo.toml b/testsuite/Cargo.toml index abeb607..b1fd1f6 100644 --- a/testsuite/Cargo.toml +++ b/testsuite/Cargo.toml @@ -20,7 +20,8 @@ cortex-m = "0.7.4" cortex-m-rt = { version = "0.7.1", default-features = false } cortex-m-semihosting = "0.5.0" futures = { version = "0.3.21", default-features = false, features = ["async-await"] } -lilos = { path = "../os", features = ["handoff"] } +lilos = { path = "../os" } +lilos-handoff = { path = "../handoff" } panic-semihosting = "0.6.0" [lib] diff --git a/testsuite/src/handoff.rs b/testsuite/src/handoff.rs index 1f9ec46..5a8caa9 100644 --- a/testsuite/src/handoff.rs +++ b/testsuite/src/handoff.rs @@ -1,4 +1,4 @@ -use lilos::handoff::Handoff; +use lilos_handoff::Handoff; pub async fn test_create_drop() { let handoff = Handoff::::new(); diff --git a/testsuite/stm32f4/Cargo.lock b/testsuite/stm32f4/Cargo.lock index 0b534ab..82c6938 100644 --- a/testsuite/stm32f4/Cargo.lock +++ b/testsuite/stm32f4/Cargo.lock @@ -155,6 +155,13 @@ dependencies = [ "cortex-m", "cortex-m-rt", "pin-project-lite", +] + +[[package]] +name = "lilos-handoff" +version = "1.0.0-pre.0" +dependencies = [ + "lilos", "scopeguard", ] @@ -167,6 +174,7 @@ dependencies = [ "cortex-m-semihosting", "futures", "lilos", + "lilos-handoff", "panic-semihosting", ] diff --git a/testsuite/stm32g0/Cargo.lock b/testsuite/stm32g0/Cargo.lock index 27faf90..923dfbd 100644 --- a/testsuite/stm32g0/Cargo.lock +++ b/testsuite/stm32g0/Cargo.lock @@ -155,6 +155,13 @@ dependencies = [ "cortex-m", "cortex-m-rt", "pin-project-lite", +] + +[[package]] +name = "lilos-handoff" +version = "1.0.0-pre.0" +dependencies = [ + "lilos", "scopeguard", ] @@ -167,6 +174,7 @@ dependencies = [ "cortex-m-semihosting", "futures", "lilos", + "lilos-handoff", "panic-semihosting", ]