1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
//! Beul - It executes futures.
//!
//! This crate offers a single function: [`execute`], which will run a single future on the current
//! thread. No fancy executor, no future primitives, just a simple executor. No dependencies, no
//! unsafe rust.
//!
//! The design is based on the example `ThreadWaker` from the [`Wake`] documentation, which the
//! reentrancy issues resolved by using a [`Condvar`].
//!
//! Beul is Dutch for executioner.
//!
//! # Usage
//!
//! ```
//! beul::execute(async {});
//! ```
#![forbid(unsafe_code)]
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::Condvar;
use std::sync::Mutex;
use std::sync::PoisonError;
use std::task::Context;
use std::task::Poll;
use std::task::Wake;
use std::task::Waker;
#[derive(Default)]
struct CondvarWake {
park: Condvar,
awoken: Mutex<bool>,
}
impl CondvarWake {
pub fn park(&self) {
let mut guard = self.awoken.lock().unwrap_or_else(PoisonError::into_inner);
// Until we are awoken, we can park on the condvar. This also handles the case where we're
// awoken while we're actually polling.
while !*guard {
guard = self
.park
.wait(guard)
.unwrap_or_else(PoisonError::into_inner);
}
*guard = false;
}
}
impl Wake for CondvarWake {
fn wake(self: Arc<Self>) {
self.wake_by_ref()
}
fn wake_by_ref(self: &Arc<Self>) {
*self.awoken.lock().unwrap_or_else(PoisonError::into_inner) = true;
self.park.notify_one();
}
}
/// Block on specified [`Future`].
///
/// The future will be polled until completion on the current thread.
pub fn execute<T>(f: impl Future<Output = T>) -> T {
// Use dynamic dispatch to save on codegen
poll(std::pin::pin!(f))
}
/// Poll a future until completion.
fn poll<T>(mut pinned: Pin<&mut dyn Future<Output = T>>) -> T {
let wake = Arc::new(CondvarWake::default());
let waker = Waker::from(Arc::clone(&wake));
let mut context = Context::from_waker(&waker);
loop {
match pinned.as_mut().poll(&mut context) {
Poll::Ready(value) => return value,
Poll::Pending => wake.park(),
}
}
}