matklad / once_cell

Rust library for single assignment cells and lazy statics without macros
Apache License 2.0
1.84k stars 110 forks source link

regression with portable-atomics in `1.20.0` #264

Open kaspar030 opened 5 days ago

kaspar030 commented 5 days ago

260 propagates the critical-section feature to portable-atomics, which conflicts with unsafe-assume-single-core.

  • propagate critical-section feature selection into portable-atomic

This breaks our use case. portable-atomic treats critical-section and unsafe-assume-single-core as mutually exclusive. We depend on esp-rs, which for some platforms (e.g., esp32c2, c3) explicitly enables the latter.

It is my understanding that libraries should not set either critical-section or unsafe-assume-single-core, but leave that to the end user.

260 states this:

ORIGINAL RATIONALE RE: propagate critical-section feature selection into portable-atomic

Normally just using critical-section should be good enough for targets like super-outdated thumbv6m-none-eabi target. But in https://github.com/rustls/rustls/pull/2088 I would like to preserve existing use of alloc option, in which case I had to add explicit portable-atomic dependency with its critical-section feature enabled.

IMO it would be nice if I didn't have to add explicit sub-depencencies with this option. This is my proposal to enable critical-section for portable-atomic if portable-atomic is wanted.

Please let me know if you have any other idea or perspective concerning this.

What does critical-section on portable-atomics actually enable that only critical-section feature enable? Maybe this can be solved in portable-atomic with a backend-agnostic feature like require-cas?

taiki-e commented 5 days ago

FYI, my recommendation (as a maintainer of portable-atomic) here is adding "portable-atomic?/require-cas" to race feature to have the appropriate error message displayed in cases such as the one @brodycj encountered, instead of always enabling the critical-section feature of the portable-atomic.

Relevant documentations are:


portable-atomic treats critical-section and unsafe-assume-single-core as mutually exclusive.

By the way, the context on these being mutually exclusive is https://github.com/taiki-e/portable-atomic/pull/51 and https://github.com/taiki-e/portable-atomic/pull/94. I suppose it is possible to resolve those priority issues by making critical-section win and not be mutually exclusive, but in any case, here I would recommend making the critical-section feature of portable-atomic optional to allow for performance and code size optimization.

matklad commented 5 days ago

Thanks for headsup @kaspar030 and cc @brodycj

I guess I need to spend more time to understand what's going on here, but it looks like the latest release is in fact incorrect, so I will revert it in a couple of hours.

matklad commented 5 days ago

Yanked 1.20, as this is indeed a regression and is not something which should be happening in such a widely used crate.

kaspar030 commented 5 days ago

Thanks!

matklad commented 5 days ago

Still, I think I am not understanding something:

once_cell is using both critical_section and portable_atomic:

It sounds like you get in a situation where:

Shouldn't portable-atomic in this case rely on critical section itself? I guess these are actually orthogonal:

If that is the case, than it seems once_cell should actually have two public featurse:

And that the users can enable either or both of this features.

matklad commented 5 days ago

Sorry for a rambling question, let me condencse this! Am I correct that the following situation is not a bug:

An embdded application is using portable-atomics with unsafe-assume-single-core feature enabled, but it also uses critical-section and provides an interrupt-disabling implementaion of critical section?

brodycj commented 5 days ago

@taiki-e I think your insight might be really helpful. My understanding is that we should try to offer as much flexibility as possible for higher-level librarians and applications that may have any combination of features enabled, or combination of multiple libraries that may have any combination of features enabled. For example: rustls currently uses once_cell, may or may not want to have critical-section enabled to support using portable-atomic with critical-section to help support building for thumbv6 ... rustls does currently use OnceBox for (via race?) no-std, maybe rustls should offer the "unsafe" single core option as well?

I am also starting to wonder if we should try to work more closely with rust-embedded to find an easier-to-understand way to get some of these options working consistently throughout multiple layers of libraries like this?

kaspar030 commented 5 days ago

An embdded application is using portable-atomics with unsafe-assume-single-core feature enabled, but it also uses critical-section and provides an interrupt-disabling implementaion of critical section?

Correct, that's not a bug. portable-atomic might provide the atomic operations via some single-core assuming shortcut (without disabling interrupts), while critical-section might disable interrupts for (longer) critical sections. portable-atomic might also just use critical sections though. It all depends. :)

I guess the fundamental thing here is that libraries should not make those choices, but leave them to the environment where they are used.

I am also starting to wonder if we should try to work more closely with rust-embedded to find an easier-to-understand way to get some of these options working consistently throughout multiple layers of libraries like this?

Actually the docs of both portable-atomic and critical-section are pretty clear for libraries - better don't select any options that select specific implementations.

It's just that most libraries turn into applications for their tests - there, choices need to be made ...

kaspar030 commented 4 days ago
  • portable_atomic, that enables race module

IMO it should be the other way around. The race feature should add the portable-atomic dependency (as suggested by @taiki-e).

portable-atomic already provides the equivalent of this, so at the price of an extra dependency, unconditionally using portable-atomic would also just work everywhere (portable-atomic passes through core::sync atomics where possible).

taiki-e commented 4 days ago

Here is my understanding on the implementation before https://github.com/matklad/once_cell/pull/260 (i.e., 1.19.0):

First, there are two no-std compatible thread-safe implementations and each depends on different things:

And I think there are two issues:

For the first issue, it is better to allow using portable-atomic without critical-section feature. I think there are two ways:

(Note that critical-section feature needs to continue to have portable-atomic enabled for compatibility with previous releases.)

For the second issue, adding require-cas feature as above is better, but I have found that it is possible to display decent error messages without require-cas feature using #[diagnostic::on_unimplemented] stabilized in Rust 1.78: https://github.com/taiki-e/portable-atomic/issues/180

@brodycj

maybe rustls should offer the "unsafe" single core option as well?

I don't think a feature for unsafe-assume-single-core in rustls is needed. It has various associated features that control its behavior (annoying to forward all) and it can also be set globally via cfg.