pollster/lib.rs
1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3
4use std::{
5 future::{Future, IntoFuture},
6 sync::{Arc, Condvar, Mutex},
7 task::{Context, Poll, Wake, Waker},
8};
9
10#[cfg(feature = "macro")]
11pub use pollster_macro::{main, test};
12
13/// An extension trait that allows blocking on a future in suffix position.
14pub trait FutureExt: Future {
15 /// Block the thread until the future is ready.
16 ///
17 /// # Example
18 ///
19 /// ```
20 /// use pollster::FutureExt as _;
21 ///
22 /// let my_fut = async {};
23 ///
24 /// let result = my_fut.block_on();
25 /// ```
26 fn block_on(self) -> Self::Output where Self: Sized { block_on(self) }
27}
28
29impl<F: Future> FutureExt for F {}
30
31enum SignalState {
32 Empty,
33 Waiting,
34 Notified,
35}
36
37struct Signal {
38 state: Mutex<SignalState>,
39 cond: Condvar,
40}
41
42impl Signal {
43 fn new() -> Self {
44 Self {
45 state: Mutex::new(SignalState::Empty),
46 cond: Condvar::new(),
47 }
48 }
49
50 fn wait(&self) {
51 let mut state = self.state.lock().unwrap();
52 match *state {
53 // Notify() was called before we got here, consume it here without waiting and return immediately.
54 SignalState::Notified => *state = SignalState::Empty,
55 // This should not be possible because our signal is created within a function and never handed out to any
56 // other threads. If this is the case, we have a serious problem so we panic immediately to avoid anything
57 // more problematic happening.
58 SignalState::Waiting => {
59 unreachable!("Multiple threads waiting on the same signal: Open a bug report!");
60 }
61 SignalState::Empty => {
62 // Nothing has happened yet, and we're the only thread waiting (as should be the case!). Set the state
63 // accordingly and begin polling the condvar in a loop until it's no longer telling us to wait. The
64 // loop prevents incorrect spurious wakeups.
65 *state = SignalState::Waiting;
66 while let SignalState::Waiting = *state {
67 state = self.cond.wait(state).unwrap();
68 }
69 }
70 }
71 }
72
73 fn notify(&self) {
74 let mut state = self.state.lock().unwrap();
75 match *state {
76 // The signal was already notified, no need to do anything because the thread will be waking up anyway
77 SignalState::Notified => {}
78 // The signal wasn't notified but a thread isn't waiting on it, so we can avoid doing unnecessary work by
79 // skipping the condvar and leaving behind a message telling the thread that a notification has already
80 // occurred should it come along in the future.
81 SignalState::Empty => *state = SignalState::Notified,
82 // The signal wasn't notified and there's a waiting thread. Reset the signal so it can be wait()'ed on again
83 // and wake up the thread. Because there should only be a single thread waiting, `notify_all` would also be
84 // valid.
85 SignalState::Waiting => {
86 *state = SignalState::Empty;
87 self.cond.notify_one();
88 }
89 }
90 }
91}
92
93impl Wake for Signal {
94 fn wake(self: Arc<Self>) {
95 self.notify();
96 }
97
98 fn wake_by_ref(self: &Arc<Self>) {
99 self.notify();
100 }
101}
102
103/// Block the thread until the future is ready.
104///
105/// # Example
106///
107/// ```
108/// let my_fut = async {};
109/// let result = pollster::block_on(my_fut);
110/// ```
111pub fn block_on<F: IntoFuture>(fut: F) -> F::Output {
112 let mut fut = core::pin::pin!(fut.into_future());
113
114 // Signal used to wake up the thread for polling as the future moves to completion. We need to use an `Arc`
115 // because, although the lifetime of `fut` is limited to this function, the underlying IO abstraction might keep
116 // the signal alive for far longer. `Arc` is a thread-safe way to allow this to happen.
117 // TODO: Investigate ways to reuse this `Arc<Signal>`... perhaps via a `static`?
118 let signal = Arc::new(Signal::new());
119
120 // Create a context that will be passed to the future.
121 let waker = Waker::from(Arc::clone(&signal));
122 let mut context = Context::from_waker(&waker);
123
124 // Poll the future to completion
125 loop {
126 match fut.as_mut().poll(&mut context) {
127 Poll::Pending => signal.wait(),
128 Poll::Ready(item) => break item,
129 }
130 }
131}