🎶🍄 “Dancing mushroom” — an async runtime construction kit.
✨ as seen in the rust compiler test suite!
Unlike other async runtime implementations,
maitake does not provide a
complete, fully-functional runtime implementation. Instead, it provides reusable
implementations of common functionality, including a task system,
scheduler, a timer wheel, and synchronization primitives.
These components may be combined with other runtime services, such as timers and
I/O resources, to produce a complete, application-specific async runtime.
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!
maitake currently provides the following major API components:
maitaketask system. This module contains the
Tasktype, representing an asynchronous task (a
Futurethat can be spawned on the runtime), and the
TaskReftype, a reference-counted, type-erased pointer to a spawned
Additionally, it also contains other utility types for working with tasks. These include the
JoinHandletype, which can be used to await the output of a task once it has been spawned, and the
task::Buildertype, for configuring a task prior to spawning it.
maitake::scheduler: schedulers for executing tasks. In order to actually execute asynchronous tasks, one or more schedulers is required. This module contains the
StaticSchedulertypes, which implement task schedulers, and utilities for constructing and using schedulers.
maitake::time: timers and futures for tracking time. This module contains tools for waiting for time-based events in asynchronous systems. It provides the
Futurewhich completes after a specified duration, and the
Timeouttype, which wraps another
Futureand cancels it if it runs for longer than a specified duration without completing.
In order to use these futures, a system must have a timer. The
maitake::timemodule therefore provides the
Timertype, a hierarchical timer wheel which can track and notify a large number of time-based futures efficiently. A
Timermust be driven by a hardware time source, such as an interrupt or timestamp counter.
maitake::sync: asynchronous synchronization primitives. This module provides asynchronous implementations of common synchronization primitives, including a
Semaphore. Additionally, it provides lower-level synchronization types which may be useful when implementing custom synchronization strategies.
maitake 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
#![no_std] by default, and the use of
liballoc is feature-flagged
for systems where
liballoc is unavailable.
This intended use case has some important implications:
This is in contrast to other async runtimes, like
glommio, which provide everything a userspace application needs to run
async tasks and perform asynchronous IO, with lower-level implementation
details encapsulated behind the runtime’s API. In the bare-metal systems
maitake is intended for use in, however, it is often necessary to have more
direct control over lower-level implementation details of the runtime.
For example: in an asynchronous runtime, tasks must be stored in non-stack
memory. Runtimes like
async-std use the standard library’s
Box type to allocate tasks on the heap. In bare-metal systems,
liballoc’s heap allocator may not be available. Such a system may
have no ability to perform dynamic heap allocations, or may implement its own
allocator which may not be compatible with
maitake is designed to still be usable in those cases — even a system
which cannot dynamically allocate memory could use
maitake in order to
create and schedule a fixed set of tasks that are stored in
allocating tasks at compile-time. Therefore,
maitake provides an
interface for overriding the memory container in which tasks are
stored. In order to provide such an interface, however,
maitake must expose
the in-memory representation of spawned tasks, which other runtimes
typically do not make part of their public APIs.
Rust supports multiple modes of handling panics:
panic="unwind". When a program is compiled with
panic="unwind", panics are
handled by unwinding the stack of the panicking thread. This allows the use of
catch_unwind, which allows panics to be handled without
terminating the entire program. On the other hand, compiling with
panic="abort" means that all panics immediately terminate the program.
Bare-metal systems typically do not use stack unwinding. For programs which use
the Rust standard library, support for unwinding is provided by
#![no_std] systems, it is necessary for the system to implement
its own unwinding system. Therefore,
maitake does not support unwinding.
This is important to note, because supporting unwinding imposes additional
safety considerations. In order to safely support unwinding, many
maitake, such as runtime internals and synchronization primitives,
would have to take extra steps to ensure that they cannot be left in an invalid
state during unwinding. Ensuring unwind-safety would require the use of standard
library APIs that are not available without
maitake does not ensure
This means that
maitake should not be used in programs compiled with
panic="unwind". Typically, no bare-metal program will fall into this category,
but if you are using
maitake in a project which uses
std, it is necessary to
explicitly disable unwinding in that project’s
maitake is a platform-agnostic library. It does not interact
directly with the underlying hardware, or use platform-specific features (with
one small exception). Instead,
maitake provides portable implementations of
core runtime components. In some cases, such as the timer wheel,
downstream code must integrate
maitake’s APIs with hardware-specific code for
in order to use them effectively.
However, one aspect of
maitake’s implementation may differ slightly across
different target architectures:
maitake 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
maitake 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 don’t need to be aware of
maitake’s use of
portable-atomic. If compiling
maitake for a target architecture that has
native support for 64-bit atomic operations (such as
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
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.,
thumbv5te), and RISC-V targets without the A extension. On these
architectures, the user must manually enable the
--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
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.
The following features are available (this list is incomplete; you can help by expanding it.)
|Inhibits cache padding for the
CachePadded struct. When this feature is NOT enabled, the size will be determined based on target platform.
|Enables support for v0.1.x of
tracing (the current release version). Requires
|Enables support for the upcoming v0.2 of
tracing (via a Git dependency).
|Enables implementations of the
core::error::Error trait for
maitake’s error types. Requires a nightly Rust toolchain.
- Schedulers for executing tasks.
- Asynchronous synchronization primitives
- Utilities for tracking time and constructing system timers.
- Safely constructs a new
StaticSchedulerinstance in a