use std::{
future::{Future, IntoFuture},
sync::Arc,
task::{Context, Poll, Wake, Waker},
};
use parking_lot::{Condvar, Mutex};
enum SignalState {
Empty,
Waiting,
Notified,
}
struct Signal {
state: Mutex<SignalState>,
cond: Condvar,
}
fn block_in_place<R>(f: impl FnOnce() -> R) -> R {
#[cfg(feature = "tokio")]
if let Ok(handle) = tokio::runtime::Handle::try_current()
&& handle.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread
{
return tokio::task::block_in_place(f);
}
f()
}
impl Signal {
fn new() -> Self {
Self {
state: Mutex::new(SignalState::Empty),
cond: Condvar::new(),
}
}
fn wait(&self) {
let mut state = self.state.lock();
match *state {
SignalState::Notified => *state = SignalState::Empty,
SignalState::Waiting => {
unreachable!("Multiple threads waiting on the same signal: Open a bug report!");
}
SignalState::Empty => {
*state = SignalState::Waiting;
block_in_place(|| {
self.cond
.wait_while(&mut state, |s| matches!(*s, SignalState::Waiting))
})
}
}
}
fn notify(&self) {
let mut state = self.state.lock();
match *state {
SignalState::Notified => {}
SignalState::Empty => *state = SignalState::Notified,
SignalState::Waiting => {
*state = SignalState::Empty;
self.cond.notify_one();
}
}
}
}
impl Wake for Signal {
fn wake(self: Arc<Self>) {
self.notify();
}
fn wake_by_ref(self: &Arc<Self>) {
self.notify();
}
}
pub fn block_on<F: IntoFuture>(fut: F) -> F::Output {
let mut fut = core::pin::pin!(fut.into_future());
let signal = Arc::new(Signal::new());
let waker = Waker::from(Arc::clone(&signal));
let mut context = Context::from_waker(&waker);
loop {
match fut.as_mut().poll(&mut context) {
Poll::Pending => signal.wait(),
Poll::Ready(item) => break item,
}
}
}