Skip to content

fix(maitake-sync): don't enable portable-atomic/critical-section#561

Merged
hawkw merged 3 commits intohawkw:mainfrom
okhsunrog:fix/critical-section-portable-atomic
Mar 13, 2026
Merged

fix(maitake-sync): don't enable portable-atomic/critical-section#561
hawkw merged 3 commits intohawkw:mainfrom
okhsunrog:fix/critical-section-portable-atomic

Conversation

@okhsunrog
Copy link
Contributor

Summary

  • The critical-section feature in both maitake-sync and maitake was enabling portable-atomic/critical-section, which conflicts with portable-atomic/unsafe-assume-single-core (used by e.g. esp-hal for ESP32-C3)
  • Per portable-atomic's docs, libraries should leave the portable-atomic backend choice to the end user
  • Updated the no-atomics CI check to explicitly pass portable-atomic/critical-section, simulating what an end user on a no-CAS target would do

Fixes #559

The `critical-section` feature in both `maitake-sync` and `maitake`
was enabling `portable-atomic/critical-section`, which conflicts with
`portable-atomic/unsafe-assume-single-core` (used by e.g. esp-hal).

Per portable-atomic's docs, libraries should leave the backend choice
to the end user. Remove the forwarded feature activation so the two
can coexist.

Fixes hawkw#559
Copy link
Owner

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think I'm okay with this change, as I discussed here:

However, there are a few additional notes before we can merge this. In particular, our README currently states that the critical-section feature flag also enables portable-atomic/critical-section, which it now no longer does:

| `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](#support-for-atomic-operations)) |

We should remove that --- and maybe add an explicit note that we don't enable it in the documentation for this feature.

We may also want to add some more discussion in the documentation here:

### 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`][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][interrupt-cfgs].
[`AtomicU64`]: https://doc.rust-lang.org/stable/core/sync/atomic/struct.AtomicU64.html
[`portable-atomic`]: https://crates.io/crates/portable-atomic
[`RUSTFLAGS`]: https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
[single-core]: https://docs.rs/portable-atomic/latest/portable_atomic/#optional-cfg
[interrupt-cfgs]: https://github.com/taiki-e/portable-atomic/blob/HEAD/src/imp/interrupt/README.md
### 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`][`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][overriding] for more
details.
[`blocking`]:
https://docs.rs/maitake-sync/latest/maitake_sync/blocking/index.html
[`blocking::Mutex`]:
https://docs.rs/maitake-sync/latest/maitake_sync/blocking/struct.Mutex.html
[`blocking::RwLock`]:
https://docs.rs/maitake-sync/latest/maitake_sync/blocking/struct.RwLock.html
[`DefaultMutex`]:
https://docs.rs/maitake-sync/latest/maitake_sync/blocking/struct.DefaultMutex.html
[spinlock]: https://en.wikipedia.org/wiki/Spinlock
[`RawMutex`]:
https://docs.rs/mutex-traits/latest/mutex_traits/trait.RawMutex.html
[`ScopedRawMutex`]:
https://docs.rs/mutex-traits/latest/mutex_traits/trait.ScopedRawMutex.html
[`mutex-traits`]: https://crates.io/crates/mutex-traits
[`lock_api`]: https://crates.io/crates/lock_api
[`critical-section`]: https://crates.io/crates/critical-section
[overriding]:
https://docs.rs/maitake-sync/latest/maitake_sync/blocking/index.html#overriding-mutex-implementations

I just want to make sure that users understand what they have to do to get everything using either critical-section or whatever else is required on their platform consistently.

# targets without native CAS atomics, portable-atomic needs a backend.
# The maitake-sync crate intentionally does NOT enable this feature
# itself (see hawkw/mycelium#559), leaving the choice to the end user.
run: cargo check -p maitake-sync --target thumbv6m-none-eabi --no-default-features --features=critical-section,portable-atomic/critical-section
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, sorry: this is a super long line, could we maybe wrap it to make it less hard to read?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, wrapped the line.

Update documentation in both maitake-sync and maitake READMEs to
reflect that the critical-section feature no longer enables
portable-atomic/critical-section. Users on no-CAS targets must now
configure a portable-atomic backend themselves.
@okhsunrog
Copy link
Contributor Author

@hawkw could we please have a new release of maitake-sync after this one gets merged?.. It's currently blocking my PR to ergot

@hawkw
Copy link
Owner

hawkw commented Mar 12, 2026

Sure, I'll publish a release. Unfortunately this will be a semver-breaking change, so please keep that in mind when updating.

@okhsunrog
Copy link
Contributor Author

I think the CI needs an approval to run

@okhsunrog
Copy link
Contributor Author

@hawkw do I need to do anything else here or can it be merged?

Copy link
Owner

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the documentation changes, I think this looks good. I'll publish a release once this makes it through CI.

@hawkw hawkw enabled auto-merge (squash) March 13, 2026 16:50
@hawkw hawkw merged commit 37ce233 into hawkw:main Mar 13, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[maitake-sync]: critical-section feature shouldn't set portable-atomic/critical-section

2 participants