Crate maitake_sync
source ·Expand description
maitake-sync
🎶🍄 “Dancing mushroom” — asynchronous synchronization
primitives from maitake
what is it?
This library is a collection of synchronization primitives for asynchronous Rust
software based on core::task
and core::future
, with a focus on
supporting #![no_std]
projects. It was initially developed as part of
maitake
, an “async runtime construction kit” intended for use in the
mycelium and mnemOS operating systems, but it may be useful for other
projects as well.
To learn a bit of the backstory behind maitake-sync
, see the announcement
post!
Note
This is a hobby project. I’m working on it in my spare time, for my own personal use. I’m very happy to share it with the broader Rust community, and contributions and bug reports are always welcome. However, please remember that I’m working on this library for fun, and if it stops being fun…well, you get the idea.
Anyway, feel free to use and enjoy this crate, and to contribute back as much as you want to!
Synchronization primitives are tools for implementing synchronization between tasks: to control which tasks can run at any given time, and in what order, and to coordinate tasks’ access to shared resources. Typically, this synchronization involves some form of waiting. In asynchronous systems, synchronization primitives allow tasks to wait by yielding to the runtime scheduler, so that other tasks may run while they are waiting.
a tour of maitake-sync
The following synchronization primitives are provided:
Mutex
: a fairly queued, asynchronous mutual exclusion lock, for protecting shared dataRwLock
: a fairly queued, asynchronous readers-writer lock, which allows concurrent read access to shared data while ensuring write access is exclusiveSemaphore
: an asynchronous counting semaphore, for limiting the number of tasks which may run concurrentlyWaitCell
, a cell that stores a single waiting task’sWaker
, so that the task can be woken by another task,WaitQueue
, a queue of waiting tasks, which are woken in first-in, first-out orderWaitMap
, a set of waiting tasks associated with keys, in which a task can be woken by its key
In addition, the util
module contains a collection of general-purpose
utilities for implementing synchronization primitives, and the blocking
module contains implementations of non-async blocking synchronization
primitives.
usage considerations
maitake-sync
is intended primarily for use in bare-metal projects, such as
operating systems, operating system components, and embedded systems. These
bare-metal systems typically do not use the Rust standard library, so
maitake-sync
supports #![no_std]
by default, and the use of liballoc
is
feature-flagged for systems where liballoc
is unavailable.
support for atomic operations
In general, maitake-sync
is a platform-agnostic library. It does not interact
directly with the underlying hardware, or use platform-specific features.
However, one aspect of maitake-sync
’s implementation may differ slightly
across different target architectures: maitake-sync
relies on atomic
operations integers. Sometimes, atomic operations on integers of specific widths
are needed (e.g., AtomicU64
), which may not be available on all architectures.
In order to work on architectures which lack atomic operations on 64-bit
integers, maitake-sync
uses the portable-atomic
crate by Taiki Endo. This
crate crate polyfills atomic operations on integers larger than the platform’s
pointer width, when these are not supported in hardware.
In most cases, users of maitake-sync
don’t need to be aware of maitake-sync
’s use of
portable-atomic
. If compiling maitake-sync
for a target architecture that has
native support for 64-bit atomic operations (such as x86_64
or aarch64
), the
native atomics are used automatically. Similarly, if compiling maitake
for any
target that has atomic compare-and-swap operations on any size integer, but
lacks 64-bit atomics (i.e., 32-bit x86 targets like i686
, or 32-bit ARM
targets with atomic operations), the portable-atomic
polyfill is used
automatically. Finally, when compiling for target architectures which lack
atomic operations because they are always single-core, such as MSP430 or AVR
microcontrollers, portable-atomic
simply uses unsynchronized operations with
interrupts temporarily disabled.
The only case where the user must be aware of portable-atomic
is when
compiling for targets which lack atomic operations but are not guaranteed to
always be single-core. This includes ARMv6-M (thumbv6m
), pre-v6 ARM (e.g.,
thumbv4t
, thumbv5te
), and RISC-V targets without the A extension. On these
architectures, the user must manually enable the RUSTFLAGS
configuration
--cfg portable_atomic_unsafe_assume_single_core
if (and only
if) the specific target hardware is known to be single-core. Enabling this cfg
is unsafe, as it will cause unsound behavior on multi-core systems using these
architectures.
Additional configurations for some single-core systems, which determine the
specific sets of interrupts that portable-atomic
will disable when entering a
critical section, are described here.
overriding blocking mutex implementations
In addition to async locks, maitake-sync
also provides a blocking
module,
which contains blocking blocking::Mutex
and blocking::RwLock
types. Many of
maitake-sync
’s async synchronization primitives, including WaitQueue
,
Mutex
, RwLock
, and Semaphore
, internally use the blocking::Mutex
type for wait-list synchronization. By default, this type uses a
blocking::DefaultMutex
as the underlying mutex
implementation, which attempts to provide the best generic mutex implementation
based on the currently enabled feature flags.
However, in some cases, it may be desirable to provide a custom mutex
implementation. Therefore, maitake-sync
’s blocking::Mutex
type, and the
async synchronization primitives that depend on it, are generic over a Lock
type parameter which may be overridden using the RawMutex
and
ScopedRawMutex
traits from the mutex-traits
crate, allowing alternative
blocking mutex implementations to be used with maitake-sync
. Using the
mutex-traits
adapters in the mutex
crate, maitake-sync
’s types may
also be used with raw mutex implementations that implement traits from the
lock_api
and critical-section
crates.
See the documentation on overriding mutex implementations for more details.
features
The following features are available (this list is incomplete; you can help by expanding it.)
Feature | Default | Explanation |
---|---|---|
alloc | true | Enables liballoc dependency |
std | false | Enables the Rust standard library, disabling #![no-std] . When std is enabled, the DefaultMutex type will use std::sync::Mutex . This implies the alloc feature. |
critical-section | false | Enables support for the critical-section crate. This includes a variant of the DefaultMutex type that uses a critical section, as well as the portable-atomic crate’s critical-section feature (as discussed above) |
no-cache-pad | false | Inhibits cache padding for the CachePadded struct. When this feature is NOT enabled, the size will be determined based on target platform. |
tracing | false | Enables support for tracing diagnostics. Requires liballoc . |
core-error | false | Enables implementations of the core::error::Error trait for maitake-sync ’s error types. Requires a nightly Rust toolchain. |
Modules
- Synchronous (blocking) synchronization primitives.
- An asynchronous mutual exclusion lock.
- An asynchronous readers-writer lock.
- An asynchronous counting semaphore.
- Synchronous spinning-based synchronization primitives.
- Reusable utilities for synchronization primitives.
- An atomically registered
Waker
, for waking a single task. - A map of
Waker
s associated with keys, so that a task can be woken by key. - A queue of waiting tasks that can be woken in first-in, first-out order (or all at once).
Structs
- An asynchronous mutual exclusion lock for protecting shared data.
- OwnedMutexGuard
alloc
- OwnedRwLockReadGuard
alloc
- An asynchronous readers-writer lock.
- An asynchronous counting semaphore.
- An atomically registered
Waker
. - A map of
Waker
s associated with keys, allowing tasks to be woken by their key. - A queue of waiting tasks which can be woken in first-in, first-out order, or all at once.