use crate::sys::Instant;
use std::future::Future;
use std::time::Duration;
const DEFAULT_STALL_SECS: u64 = 10;
const STALL_MESSAGE: &str = "some_executor::block_on has waited a long time without a single wakeup, which usually \
means it can never be woken. Two things cause that. (1) The executor runs tasks on the calling thread -- a \
current-thread or local executor -- and inherited the default SomeExecutor::block_on, which parks the caller and \
so stops the very loop the future is waiting on; such an executor must override SomeExecutor::block_on_objsafe to \
run its own polling loop instead. (2) block_on was called from inside a task already running on this executor; \
re-entrancy is not supported. If instead this future is legitimately idle, set SOME_EXECUTOR_BLOCK_ON_STALL_SECS \
to raise the threshold (default 10) or to 0 to disable this check. Set SOME_EXECUTOR_BUILTIN_SHOULD_PANIC=1 to \
panic instead of printing this.";
#[cfg(target_arch = "wasm32")]
const MAIN_THREAD_MESSAGE: &str = "some_executor::block_on was called on the wasm32 main thread, which cannot block. \
The JavaScript event loop delivers every wakeup -- timers, promises, fetch, worker messages -- and it only runs \
once this thread's stack unwinds, so blocking here starves the wakeup that would end the block. Use \
some_executor::ExecutorMain for a portable entry point, or call block_on from a worker.";
pub fn block_on<F: Future>(future: F) -> F::Output {
imp::block_on(future)
}
pub(crate) fn block_on_installed<F: Future>(
executor: Box<crate::DynExecutor>,
future: F,
) -> F::Output {
let _guard = crate::thread_executor::install_thread_executor(executor);
block_on(future)
}
struct StallWatchdog {
threshold: Option<Duration>,
last_wake: Instant,
}
impl StallWatchdog {
fn new() -> Self {
let secs = std::env::var("SOME_EXECUTOR_BLOCK_ON_STALL_SECS")
.ok()
.and_then(|v| v.trim().parse::<u64>().ok())
.unwrap_or(DEFAULT_STALL_SECS);
StallWatchdog {
threshold: (secs > 0).then(|| Duration::from_secs(secs)),
last_wake: Instant::now(),
}
}
fn budget(&self) -> Option<Duration> {
self.threshold
.map(|t| t.saturating_sub(self.last_wake.elapsed()))
}
fn woken(&mut self) {
if self.threshold.is_some() {
self.last_wake = Instant::now();
}
}
fn stalled(&mut self) -> bool {
if self.budget() != Some(Duration::ZERO) {
return false;
}
self.threshold = None;
true
}
}
#[cfg(not(target_arch = "wasm32"))]
mod imp {
use super::StallWatchdog;
use std::future::Future;
use std::pin::pin;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};
struct Signal {
woken: Mutex<bool>,
condvar: Condvar,
}
fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(|e| e.into_inner())
}
impl Wake for Signal {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
let mut woken = lock(&self.woken);
*woken = true;
self.condvar.notify_one();
}
}
pub(super) fn block_on<F: Future>(future: F) -> F::Output {
let signal = Arc::new(Signal {
woken: Mutex::new(false),
condvar: Condvar::new(),
});
let waker = Waker::from(signal.clone());
let mut context = Context::from_waker(&waker);
let mut future = pin!(future);
let mut watchdog = StallWatchdog::new();
loop {
if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
return output;
}
let mut woken = lock(&signal.woken);
while !*woken {
match watchdog.budget() {
Some(budget) => {
let (guard, timeout) = signal
.condvar
.wait_timeout(woken, budget)
.unwrap_or_else(|e| e.into_inner());
woken = guard;
if timeout.timed_out() && !*woken && watchdog.stalled() {
crate::warn_or_panic(super::STALL_MESSAGE);
}
}
None => {
woken = signal
.condvar
.wait(woken)
.unwrap_or_else(|e| e.into_inner());
}
}
}
*woken = false;
drop(woken);
watchdog.woken();
}
}
}
#[cfg(target_arch = "wasm32")]
mod imp {
use super::{MAIN_THREAD_MESSAGE, StallWatchdog};
use std::future::Future;
use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Wake, Waker};
struct Signal(AtomicBool);
impl Wake for Signal {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.store(true, Ordering::Release);
}
}
pub(super) fn block_on<F: Future>(future: F) -> F::Output {
if wasm_lite_std::is_main_thread() {
panic!("{}", MAIN_THREAD_MESSAGE);
}
let signal = Arc::new(Signal(AtomicBool::new(false)));
let waker = Waker::from(signal.clone());
let mut context = Context::from_waker(&waker);
let mut future = pin!(future);
let mut watchdog = StallWatchdog::new();
loop {
if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
return output;
}
if signal.0.swap(false, Ordering::Acquire) {
watchdog.woken();
} else if watchdog.stalled() {
crate::warn_or_panic(super::STALL_MESSAGE);
}
wasm_lite_std::yield_now();
}
}
}
#[cfg(test)]
mod tests {
use super::block_on;
use crate::sys::Instant;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::task::{Context, Poll};
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn ready_future_returns_immediately() {
assert_eq!(block_on(async { 42 }), 42);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn borrows_from_the_stack() {
let name = String::from("world");
assert_eq!(
block_on(async { format!("hello {}", name.as_str()) }),
"hello world"
);
assert_eq!(
name, "world",
"the future borrowed, it did not take ownership"
);
}
struct SelfWaking(usize);
impl Future for SelfWaking {
type Output = usize;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
if self.0 == 0 {
return Poll::Ready(0);
}
self.0 -= 1;
cx.waker().wake_by_ref();
Poll::Pending
}
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn self_waking_future_completes() {
assert_eq!(block_on(SelfWaking(5)), 0);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn polls_only_after_wake() {
struct Counting(Arc<AtomicUsize>, usize);
impl Future for Counting {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
self.0.fetch_add(1, Ordering::Relaxed);
if self.1 == 0 {
return Poll::Ready(());
}
self.1 -= 1;
cx.waker().wake_by_ref();
Poll::Pending
}
}
let polls = Arc::new(AtomicUsize::new(0));
block_on(Counting(polls.clone(), 3));
assert_eq!(polls.load(Ordering::Relaxed), 4);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn watchdog_fires_once_and_only_after_the_threshold() {
use super::StallWatchdog;
use std::time::Duration;
let mut watchdog = StallWatchdog {
threshold: Some(Duration::from_millis(50)),
last_wake: Instant::now(),
};
assert!(!watchdog.stalled());
watchdog.last_wake = Instant::now() - Duration::from_secs(1);
assert!(watchdog.stalled());
assert!(!watchdog.stalled());
assert_eq!(watchdog.budget(), None);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test(worker))]
fn watchdog_disabled_waits_indefinitely() {
use super::StallWatchdog;
let mut watchdog = StallWatchdog {
threshold: None,
last_wake: Instant::now(),
};
assert_eq!(watchdog.budget(), None);
assert!(!watchdog.stalled());
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn woken_from_another_thread() {
use std::sync::Mutex;
use std::task::Waker;
use std::time::Duration;
struct Remote(Arc<Mutex<(bool, Option<Waker>)>>);
impl Future for Remote {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<&'static str> {
let mut state = self.0.lock().unwrap();
if state.0 {
Poll::Ready("done")
} else {
state.1 = Some(cx.waker().clone());
Poll::Pending
}
}
}
let state = Arc::new(Mutex::new((false, None::<Waker>)));
let remote = state.clone();
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
let waker = {
let mut state = remote.lock().unwrap();
state.0 = true;
state.1.take()
};
if let Some(waker) = waker {
waker.wake();
}
});
assert_eq!(block_on(Remote(state)), "done");
}
}