#[cfg(not(target_arch = "wasm32"))]
use std::cell::Cell;
use std::cell::RefCell;
#[cfg(not(target_arch = "wasm32"))]
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::context::{LocalKey, LocalKeyImmutable};
#[cfg(not(target_arch = "wasm32"))]
thread_local! {
static ACTIVE_TASK_LOCAL_POLLS: Cell<usize> = const { Cell::new(0) };
static DEFERRED_UNTIL_TASK_LOCALS_RESTORED: RefCell<VecDeque<Box<dyn FnOnce()>>> =
RefCell::new(VecDeque::new());
}
#[cfg(not(target_arch = "wasm32"))]
fn enter_task_local_poll() {
ACTIVE_TASK_LOCAL_POLLS.set(ACTIVE_TASK_LOCAL_POLLS.get() + 1);
}
#[cfg(not(target_arch = "wasm32"))]
fn leave_task_local_poll() {
let depth = ACTIVE_TASK_LOCAL_POLLS.get();
debug_assert!(depth > 0, "task-local poll depth underflow");
ACTIVE_TASK_LOCAL_POLLS.set(depth - 1);
if depth != 1 {
return;
}
if std::thread::panicking() {
DEFERRED_UNTIL_TASK_LOCALS_RESTORED.with_borrow_mut(VecDeque::clear);
return;
}
loop {
let deferred = DEFERRED_UNTIL_TASK_LOCALS_RESTORED.with_borrow_mut(VecDeque::pop_front);
match deferred {
Some(deferred) => deferred(),
None => break,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn run_or_defer_until_task_locals_restored(work: impl FnOnce() + 'static) {
if ACTIVE_TASK_LOCAL_POLLS.get() == 0 {
work();
} else {
DEFERRED_UNTIL_TASK_LOCALS_RESTORED.with_borrow_mut(|deferred| {
deferred.push_back(Box::new(work));
});
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn run_or_defer_until_task_locals_restored(work: impl FnOnce() + 'static) {
work();
}
fn poll_with_scoped_value<V: 'static, F: Future>(
key: &'static std::thread::LocalKey<RefCell<Option<V>>>,
slot: &mut Option<V>,
future: Pin<&mut F>,
cx: &mut Context<'_>,
) -> Poll<F::Output> {
struct Guard<'a, V: 'static> {
key: &'static std::thread::LocalKey<RefCell<Option<V>>>,
old_value: Option<Option<V>>,
slot: &'a mut Option<V>,
}
impl<V: 'static> Drop for Guard<'_, V> {
fn drop(&mut self) {
let value = self
.key
.replace(self.old_value.take().expect("Guard dropped twice"));
*self.slot = value;
#[cfg(not(target_arch = "wasm32"))]
leave_task_local_poll();
}
}
let value = slot.take().expect("No value in slot");
let old_value = key.replace(Some(value));
#[cfg(not(target_arch = "wasm32"))]
enter_task_local_poll();
let guard = Guard {
key,
old_value: Some(old_value),
slot,
};
let r = future.poll(cx);
drop(guard);
r
}
#[derive(Debug)]
pub(crate) struct TaskLocalFuture<V: 'static, F> {
pub(crate) slot: Option<V>,
pub(crate) local_key: &'static LocalKey<V>,
pub(crate) future: F,
}
#[derive(Debug)]
pub(crate) struct TaskLocalImmutableFuture<V: 'static, F> {
pub(crate) slot: Option<V>,
pub(crate) local_key: &'static LocalKeyImmutable<V>,
pub(crate) future: F,
}
impl<V, F> TaskLocalFuture<V, F> {
}
impl<V, F> TaskLocalImmutableFuture<V, F> {
}
impl<V, F> Future for TaskLocalFuture<V, F>
where
V: Unpin,
F: Future,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (future, slot, local_key) = unsafe {
let this = self.get_unchecked_mut();
let future = Pin::new_unchecked(&mut this.future);
(future, &mut this.slot, this.local_key)
};
poll_with_scoped_value(&local_key.0, slot, future, cx)
}
}
impl<V, F> Future for TaskLocalImmutableFuture<V, F>
where
V: Unpin,
F: Future,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (future, slot, local_key) = unsafe {
let this = self.get_unchecked_mut();
let future = Pin::new_unchecked(&mut this.future);
(future, &mut this.slot, this.local_key)
};
poll_with_scoped_value(&local_key.0, slot, future, cx)
}
}
#[cfg(test)]
mod tests {
crate::task_local! {
static MUTABLE_LEVEL: u32;
static const IMMUTABLE_LEVEL: u32;
#[cfg(not(target_arch = "wasm32"))]
static const REENTRANT_SECRET: u32;
}
#[wasm_lite::wasm_lite_test]
async fn nested_scope_same_key_shadows() {
MUTABLE_LEVEL
.scope(1, async {
assert_eq!(MUTABLE_LEVEL.get(), 1);
MUTABLE_LEVEL
.scope(2, async {
assert_eq!(MUTABLE_LEVEL.get(), 2);
})
.await;
assert_eq!(MUTABLE_LEVEL.get(), 1);
})
.await;
MUTABLE_LEVEL.with(|v| assert!(v.is_none()));
}
#[wasm_lite::wasm_lite_test]
async fn nested_scope_same_key_shadows_immutable() {
IMMUTABLE_LEVEL
.scope(1, async {
assert_eq!(IMMUTABLE_LEVEL.get(), 1);
IMMUTABLE_LEVEL
.scope(2, async {
assert_eq!(IMMUTABLE_LEVEL.get(), 2);
})
.await;
assert_eq!(IMMUTABLE_LEVEL.get(), 1);
})
.await;
IMMUTABLE_LEVEL.with(|v| assert!(v.is_none()));
}
#[wasm_lite::wasm_lite_test]
async fn set_outside_scope_then_scope() {
crate::task_local! {
static OUTSIDE: u32;
}
OUTSIDE.set(7);
OUTSIDE
.scope(1, async {
assert_eq!(OUTSIDE.get(), 1);
})
.await;
OUTSIDE.with(|v| assert_eq!(v.copied(), Some(7)));
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn panic_in_scope_restores_thread_local() {
crate::task_local! {
static PANICKY: u32;
}
let result = std::panic::catch_unwind(|| {
wasm_lite_std::block_on(PANICKY.scope(42, async {
panic!("boom");
}));
});
assert!(result.is_err());
PANICKY.with(|v| assert!(v.is_none()));
wasm_lite_std::block_on(PANICKY.scope(5, async {
assert_eq!(PANICKY.get(), 5);
}));
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn reentrantly_spawned_task_does_not_inherit_task_local() {
use crate::SomeStaticExecutor;
use crate::observer::Observer;
use crate::task::{Configuration, Task};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
let child_saw_secret = Arc::new(AtomicBool::new(false));
let child_saw_secret_clone = child_saw_secret.clone();
wasm_lite_std::block_on(REENTRANT_SECRET.scope(42, async move {
let child = Task::without_notifications(
"reentrant child".to_string(),
Configuration::default(),
async move {
let inherited = REENTRANT_SECRET.with(|secret| secret.is_some());
child_saw_secret_clone.store(inherited, Ordering::Relaxed);
},
);
let mut executor = crate::static_last_resort::StaticLastResortExecutor::new();
executor.spawn_static(child).detach();
}));
assert!(
!child_saw_secret.load(Ordering::Relaxed),
"a separately spawned task inherited its parent's scoped task-local"
);
}
}