Skip to content

Commit

Permalink
Move handoff into its own crate.
Browse files Browse the repository at this point in the history
I've been considering doing this for a while, because it doesn't quite
meet the same API robustness standards as the rest of the OS -- but it's
too useful to delete.

I figured 1.0 was a good time to do it.
  • Loading branch information
cbiffle committed Mar 3, 2024
1 parent 28d0f1e commit 24c2622
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 41 deletions.
9 changes: 5 additions & 4 deletions RELEASE-NOTES.mkdn
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion build-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 8 additions & 8 deletions doc/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions handoff/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = "thumbv7em-none-eabihf"
191 changes: 191 additions & 0 deletions handoff/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions handoff/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "lilos-handoff"
version = "1.0.0-pre.0"
authors = ["Cliff L. Biffle <code@cliffle.com>"]
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
11 changes: 11 additions & 0 deletions handoff/README.mkdn
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 21 additions & 16 deletions os/src/handoff.rs → handoff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T>`, 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
Expand Down Expand Up @@ -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
Expand All @@ -49,23 +51,28 @@
//!
//! # 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
//! contexts where the future might be cancelled (such as `with_timeout`), then
//! 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.
Expand Down Expand Up @@ -148,11 +155,7 @@ impl<T> core::fmt::Debug for State<T> {
impl<T> Copy for State<T> {}
impl<T> Clone for State<T> {
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!
}
}

Expand Down Expand Up @@ -277,11 +280,13 @@ impl<T> Popper<'_, T> {
pub fn try_pop(&mut self) -> Option<T> {
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
Expand Down
2 changes: 1 addition & 1 deletion msrv-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions os/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 24c2622

Please sign in to comment.