mirror of https://github.com/stjepang/smol
76 lines
2.3 KiB
Rust
76 lines
2.3 KiB
Rust
//! Implementation of [`block_on()`].
|
|
//!
|
|
//! This is equivalent to [`futures::executor::block_on()`], but slightly more efficient.
|
|
//!
|
|
//! The implementation is explained in detail in [*Build your own block_on()*][blog-post].
|
|
//!
|
|
//! [`futures::executor::block_on()`]: https://docs.rs/futures/0.3/futures/executor/fn.block_on.html
|
|
//! [blog-post]: https://stjepang.github.io/2020/01/25/build-your-own-block-on.html
|
|
|
|
use std::cell::RefCell;
|
|
use std::future::Future;
|
|
use std::task::{Context, Poll, Waker};
|
|
|
|
use crossbeam_utils::sync::Parker;
|
|
|
|
use crate::context;
|
|
|
|
/// Blocks on a single future.
|
|
///
|
|
/// This function polls the future in a loop, parking the current thread after each step to wait
|
|
/// until its waker is woken.
|
|
///
|
|
/// Unlike [`run()`], it does not run executors or poll the reactor!
|
|
///
|
|
/// You can think of it as the easiest and most efficient way of turning an async operation into a
|
|
/// blocking operation.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use futures::future;
|
|
/// use smol::{Async, Timer};
|
|
/// use std::thread;
|
|
/// use std::time::Duration;
|
|
///
|
|
/// // Run executors and the reactor on a separeate thread, forever.
|
|
/// thread::spawn(|| smol::run(future::pending::<()>()));
|
|
///
|
|
/// smol::block_on(async {
|
|
/// // Sleep for a second.
|
|
/// // This timer only works because there's a thread calling `run()`.
|
|
/// Timer::after(Duration::from_secs(1)).await;
|
|
/// })
|
|
/// ```
|
|
///
|
|
/// [`run()`]: crate::run()
|
|
pub fn block_on<T>(future: impl Future<Output = T>) -> T {
|
|
thread_local! {
|
|
// Parker and waker associated with the current thread.
|
|
static CACHE: RefCell<(Parker, Waker)> = {
|
|
let parker = Parker::new();
|
|
let unparker = parker.unparker().clone();
|
|
let waker = async_task::waker_fn(move || unparker.unpark());
|
|
RefCell::new((parker, waker))
|
|
};
|
|
}
|
|
|
|
CACHE.with(|cache| {
|
|
// Panic if `block_on()` is called recursively.
|
|
let (parker, waker) = &*cache.borrow();
|
|
|
|
// If enabled, set up tokio before execution begins.
|
|
context::enter(|| {
|
|
futures_util::pin_mut!(future);
|
|
let cx = &mut Context::from_waker(&waker);
|
|
|
|
loop {
|
|
match future.as_mut().poll(cx) {
|
|
Poll::Ready(output) => return output,
|
|
Poll::Pending => parker.park(),
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|