#[cfg(not(all(loom, test)))]
pub(crate) use crate::mem::BlockArc;
#[cfg(all(loom, test))]
#[allow(unused_imports)]
pub(crate) use loom::sync::Arc;
#[cfg(all(loom, test))]
pub(crate) use loom::sync::Arc as BlockArc;
#[cfg(not(all(loom, test)))]
#[allow(unused_imports)]
pub(crate) use std::sync::Arc;
#[cfg(all(loom, test))]
#[allow(unused_imports)]
pub(crate) use loom::sync::atomic::{
AtomicBool, AtomicPtr, AtomicU8, AtomicU32, AtomicU64, AtomicUsize, Ordering, fence,
};
#[cfg(not(all(loom, test)))]
#[allow(unused_imports)]
pub(crate) use std::sync::atomic::{
AtomicBool, AtomicPtr, AtomicU8, AtomicU32, AtomicU64, AtomicUsize, Ordering, fence,
};
#[inline(always)]
pub(crate) fn take_ptr<T>(atomic: &mut AtomicPtr<T>) -> *mut T {
#[cfg(not(all(loom, test)))]
{
std::mem::replace(atomic.get_mut(), std::ptr::null_mut())
}
#[cfg(all(loom, test))]
{
atomic.swap(std::ptr::null_mut(), Ordering::AcqRel)
}
}
#[inline(always)]
pub(crate) fn seq_cst_handshake() {
#[cfg(all(loom, test))]
fence(Ordering::SeqCst);
}
#[cfg(any(test, not(any(unix, windows, target_os = "wasi"))))]
pub(crate) use os_mutex::Mutex;
#[cfg(any(test, not(any(unix, windows, target_os = "wasi"))))]
#[cfg_attr(all(loom, test), allow(dead_code))]
mod os_mutex {
#[cfg(all(loom, test))]
use loom::sync as imp;
#[cfg(not(all(loom, test)))]
use std::sync as imp;
use imp::MutexGuard;
use std::sync::PoisonError;
#[derive(Default)]
pub(crate) struct Mutex<T>(imp::Mutex<T>);
impl<T> Mutex<T> {
#[cfg(not(all(loom, test)))]
pub(crate) const fn new(value: T) -> Self {
Self(imp::Mutex::new(value))
}
#[cfg(all(loom, test))]
pub(crate) fn new(value: T) -> Self {
Self(imp::Mutex::new(value))
}
#[allow(dead_code)]
pub(crate) fn into_inner(self) -> T {
self.0.into_inner().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T> Mutex<T> {
pub(crate) fn lock(&self) -> MutexGuard<'_, T> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
#[allow(dead_code)]
pub(crate) fn get_mut(&mut self) -> &mut T {
self.0.get_mut().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
}
#[cfg(not(all(loom, test)))]
pub(crate) struct UnsafeCell<T>(std::cell::UnsafeCell<T>);
#[cfg(not(all(loom, test)))]
impl<T> UnsafeCell<T> {
pub(crate) const fn new(value: T) -> Self {
Self(std::cell::UnsafeCell::new(value))
}
pub(crate) fn with<R>(&self, f: impl FnOnce(*const T) -> R) -> R {
f(self.0.get())
}
pub(crate) fn with_mut<R>(&self, f: impl FnOnce(*mut T) -> R) -> R {
f(self.0.get())
}
}
#[cfg(all(loom, test))]
pub(crate) use loom::cell::UnsafeCell;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct Unpark(pub(crate) std::thread::Thread);
#[cfg(not(target_arch = "wasm32"))]
impl std::task::Wake for Unpark {
fn wake(self: std::sync::Arc<Self>) {
self.0.unpark();
}
fn wake_by_ref(self: &std::sync::Arc<Self>) {
self.0.unpark();
}
}
#[cfg(not(any(all(loom, test), target_arch = "wasm32")))]
pub(crate) fn block_on<F: core::future::Future>(future: F) -> F::Output {
use core::task::{Context, Poll};
let waker = std::task::Waker::from(std::sync::Arc::new(Unpark(std::thread::current())));
let mut cx = Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
loop {
if let Poll::Ready(output) = crate::coop::outside(|| future.as_mut().poll(&mut cx)) {
return output;
}
std::thread::park();
}
}
#[cfg(all(loom, test))]
pub(crate) fn block_on<F: core::future::Future>(future: F) -> F::Output {
use core::task::{Context, Poll};
struct Notify(loom::sync::Notify);
impl std::task::Wake for Notify {
fn wake(self: std::sync::Arc<Self>) {
self.0.notify();
}
fn wake_by_ref(self: &std::sync::Arc<Self>) {
self.0.notify();
}
}
let park = std::sync::Arc::new(Notify(loom::sync::Notify::new()));
let waker = std::task::Waker::from(std::sync::Arc::clone(&park));
let mut cx = Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
loop {
if let Poll::Ready(output) = crate::coop::outside(|| future.as_mut().poll(&mut cx)) {
return output;
}
park.0.wait();
}
}
pub(crate) fn wait_here<F: core::future::Future>(future: F) -> std::io::Result<F::Output> {
#[cfg(not(target_arch = "wasm32"))]
{
Ok(block_on(future))
}
#[cfg(target_arch = "wasm32")]
{
use core::task::{Context, Poll};
let mut future = core::pin::pin!(future);
match crate::coop::outside(|| {
future
.as_mut()
.poll(&mut Context::from_waker(std::task::Waker::noop()))
}) {
Poll::Ready(output) => Ok(output),
Poll::Pending => Err(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"this call waits for something its own thread holds; finish that first",
)),
}
}
}
pub(crate) fn during_unwind(f: impl FnOnce()) {
if let Some(panic) = crate::rt::caught::catch(f) {
crate::rt::caught::drop_payload(panic);
}
}
#[must_use = "dropped at once, it runs its give-back"]
pub(crate) struct OnUnwind<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> OnUnwind<F> {
#[inline(always)]
pub(crate) fn new(undo: F) -> Self {
Self(Some(undo))
}
#[inline(always)]
pub(crate) fn disarm(self) {
core::mem::forget(self);
}
}
impl<F: FnOnce()> Drop for OnUnwind<F> {
#[cold]
fn drop(&mut self) {
if let Some(undo) = self.0.take() {
during_unwind(undo);
}
}
}
pub(crate) trait Withdraw {
fn withdraw(self: core::pin::Pin<&mut Self>);
}
#[inline(always)]
pub(crate) fn freeze_frames() {
#[cfg(not(any(all(loom, test), target_arch = "wasm32")))]
if super::frames::any() {
super::frames::freeze();
}
}
#[expect(
clippy::panic,
reason = "documented under `# Panics`: a wait that would park the only thread"
)]
pub(crate) fn blocking_in_line<F: core::future::Future + Withdraw>(future: F) -> F::Output {
#[cfg(not(any(all(loom, test), target_arch = "wasm32")))]
{
if crate::rt::context::current(|current| current.is_some()) {
return super::frames::in_line(future, |frame| crate::runtime::block_on(frame));
}
if let Some(shard) = crate::rt::context::own_shard_if_made() {
let _entered = crate::rt::context::enter(&shard);
return super::frames::in_line(future, |frame| shard.block_on_then_leave(frame));
}
}
match wait_here(future) {
Ok(output) => output,
Err(stuck) => panic!("{stuck}"),
}
}
#[expect(
clippy::panic,
reason = "documented under `# Panics`: a wait that would park the only thread"
)]
pub(crate) fn blocking<F: core::future::Future>(future: F) -> F::Output {
#[cfg(not(any(all(loom, test), target_arch = "wasm32")))]
{
if crate::rt::context::current(|current| current.is_some())
|| (crate::rt::context::lost_a_shard() && crate::rt::context::own_shard().is_ok())
{
return crate::runtime::block_on(future);
}
if let Some(shard) = crate::rt::context::own_shard_if_made() {
let _entered = crate::rt::context::enter(&shard);
return shard.block_on_then_leave(future);
}
}
match wait_here(future) {
Ok(output) => output,
Err(stuck) => panic!("{stuck}"),
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
#[test]
#[allow(unsafe_code)]
fn the_unsafe_cell_shim_round_trips() {
let cell = UnsafeCell::new(vec![1u32, 2, 3]);
cell.with_mut(|ptr| unsafe { (*ptr).push(4) });
let read = cell.with(|ptr| unsafe { (*ptr).clone() });
assert_eq!(read, vec![1, 2, 3, 4]);
}
#[test]
fn a_lock_whose_holder_panicked_still_hands_back_its_contents() {
let mutex = Arc::new(Mutex::new(vec![1u32, 2, 3]));
let poisoner = Arc::clone(&mutex);
let panicked = std::thread::spawn(move || {
let mut guard = poisoner.lock();
guard.push(4);
panic!("the holder dies mid-update");
})
.join();
assert!(panicked.is_err(), "the probe needs the thread to panic");
assert_eq!(
*mutex.lock(),
vec![1, 2, 3, 4],
"poisoning must not hide what the panicking thread had already written",
);
}
#[test]
fn a_wait_inside_a_spent_budget_still_polls_its_future() {
let (done, answer) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let previous = crate::coop::enter();
while crate::coop::try_spend() {}
let out = block_on(core::future::poll_fn(|cx| {
if crate::coop::spend(cx.waker()) {
core::task::Poll::Ready(7)
} else {
core::task::Poll::Pending
}
}));
crate::coop::leave(previous);
let _ = done.send(out);
});
assert_eq!(
answer.recv_timeout(std::time::Duration::from_secs(10)),
Ok(7),
"block_on spun on the spent budget of the poll around it"
);
}
}