Optimize away poison guards when std is built with panic=abort
> **Note**: To take advantage of this PR, you will have to use `-Zbuild-std` or build your own toolchain. rustup toolchains always link to a libstd that was compiled with `panic=unwind`, since it's compatible with `panic=abort` code.
When std is compiled with `panic=abort` we can remove a lot of the poison machinery from the locks. This changes the `Flag` and `Guard` types to be ZSTs. It also adds an uninhabited member to `PoisonError` so the compiler knows it can optimize away the `Result::Err` paths, and make `LockResult<T>` layout-equivalent to `T`.
### Is this a breaking change?
`PoisonError::new` now panics if invoked from a libstd built with `panic="abort"` (or any non-`unwind` strategy). It is unclear to me whether to consider this a breaking change.
In order to encounter this behavior, **both of the following must be true**:
#### Using a libstd with `panic="abort"`
This is pretty uncommon. We don't build libstd with that in rustup, except in (Tier 2-3) platforms that do not support unwinding, **most notably wasm**.
Most people who do this are using cargo's `-Z build-std` feature, which is unstable.
`panic="abort"` is not a supported option in Rust's build system. It is possible to configure it using `CARGO_TARGET_xxx_RUSTFLAGS`, but I believe this only works on **non-host** platforms.
#### Creating `PoisonError` manually
This is also unlikely. The only common use case I can think of is in tests, and you can't run tests with `panic="abort"` without the unstable `-Z panic_abort_tests` flag.
It's possible that someone is implementing their own locks using std's `PoisonError` **and** defining "thread failure" to mean something other than "panic". If this is the case then we would break their code if it was used with a `panic="abort"` libstd. The locking crates I know of don't replicate std's poison API, but I haven't done much research into this yet.
I've touched on a fair number of considerations here. Which ones do people consider relevant?
remove redundant imports
detects redundant imports that can be eliminated.
for #117772 :
In order to facilitate review and modification, split the checking code and removing redundant imports code into two PR.
r? `@petrochenkov`
detects redundant imports that can be eliminated.
for #117772 :
In order to facilitate review and modification, split the checking code and
removing redundant imports code into two PR.
`wait_while` takes care of spurious wake-ups in centralized place,
reducing chances for mistakes and potential future optimizations
(who knows, maybe in future there will be no spurious wake-ups? :)
Make `Debug` representations of `[Lazy, Once]*[Cell, Lock]` consistent with `Mutex` and `RwLock`
`Mutex` prints `<locked>` as a field value when its inner value cannot be accessed, but the lazy types print a fixed string like "`OnceCell(Uninit)`". This could cause confusion if the inner type is a unit type named `Uninit` and does not respect the pretty-printing flag. With this change, the format message is now "`OnceCell(<uninit>)`", consistent with `Mutex`.
Use `LazyLock` to lazily resolve backtraces
By using TAIT to name the initializing closure, `LazyLock` can be used to replace the current `LazilyResolvedCapture`.
Spelling library
Split per https://github.com/rust-lang/rust/pull/110392
I can squash once people are happy w/ the changes. It's really uncommon for large sets of changes to be perfectly acceptable w/o at least some changes.
I probably won't have time to respond until tomorrow or the next day
`Mutex` prints `<locked>` as a field value when its inner value cannot be accessed, but the lazy types print a fixed string like "`OnceCell(Uninit)`". This could cause confusion if the inner type is a unit type named `Uninit` and does not respect the pretty-printing flag. With this change, the format message is now "`OnceCell(<uninit>)`", consistent with `Mutex`.
Receiver disconnection relies on the incorrect assumption that
`head.index != tail.index` implies that the channel is initialized (i.e
`head.block` and `tail.block` point to allocated blocks). However, it
can happen that `head.index != tail.index` and `head.block == null` at
the same time which leads to a segfault when a channel is dropped in
that state.
This can happen because initialization is performed in two steps. First,
the tail block is allocated and the `tail.block` is set. If that is
successful `head.block` is set to the same pointer. Importantly,
initialization is skipped if `tail.block` is not null.
Therefore we can have the following situation:
1. Thread A starts to send the first value of the channel, observes that
`tail.block` is null and begins initialization. It sets `tail.block`
to point to a newly allocated block and then gets preempted.
`head.block` is still null at this point.
2. Thread B starts to send the second value of the channel, observes
that `tail.block` *is not* null and proceeds with writing its value
in the allocated tail block and sets `tail.index` to 1.
3. Thread B drops the receiver of the channel which observes that
`head.index != tail.index` (0 and 1 respectively), therefore there
must be messages to drop. It starts traversing the linked list from
`head.block` which is still a null pointer, leading to a segfault.
This PR fixes this problem by waiting for initialization to complete
when `head.index != tail.index` and the `head.block` is still null. A
similar check exists in `start_recv` for similar reasons.
Fixes#110001
Signed-off-by: Petros Angelatos <petrosagg@gmail.com>