pub mod mpsc;
pub mod oneshot;
#[cfg(spawned_loom)]
use loom::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
#[cfg(not(spawned_loom))]
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
use std::sync::{mpsc as std_mpsc, OnceLock};
pub use std::{
future::Future,
thread::{sleep, spawn, JoinHandle},
};
#[cfg(feature = "async")]
use crate::tasks::Runtime;
use crate::tracing::init_tracing;
static CTRL_C_SUBSCRIBERS: OnceLock<Mutex<Vec<std_mpsc::Sender<()>>>> = OnceLock::new();
pub fn run(f: fn()) {
init_tracing();
f()
}
#[cfg(feature = "async")]
pub fn block_on<F: Future>(future: F) -> F::Output {
#[cfg(feature = "backend-experiments")]
if std::env::var("SPAWNED_EXPERIMENT_RUNTIME").as_deref() == Ok("managed") {
static POOL: OnceLock<Runtime> = OnceLock::new();
return POOL
.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap()
})
.block_on(future);
}
#[cfg(feature = "backend-experiments")]
let rt = match std::env::var("SPAWNED_EXPERIMENT_RUNTIME").as_deref() {
Ok("current") => tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap(),
Ok("one-worker") => tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap(),
_ => isolated_runtime(),
};
#[cfg(not(feature = "backend-experiments"))]
let rt = isolated_runtime();
rt.block_on(future)
}
#[cfg(feature = "async")]
fn isolated_runtime() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap()
}
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
spawn(f)
}
type CancelCallback = Box<dyn FnOnce() + Send>;
#[derive(Clone)]
pub struct CancellationToken {
is_cancelled: Arc<AtomicBool>,
callbacks: Arc<Mutex<Vec<CancelCallback>>>,
}
impl std::fmt::Debug for CancellationToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CancellationToken")
.field("is_cancelled", &self.is_cancelled())
.finish()
}
}
impl Default for CancellationToken {
fn default() -> Self {
Self::new()
}
}
impl CancellationToken {
pub fn new() -> Self {
CancellationToken {
is_cancelled: Arc::new(false.into()),
callbacks: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn is_cancelled(&self) -> bool {
self.is_cancelled.load(Ordering::SeqCst)
}
pub fn cancel(&self) {
self.is_cancelled.store(true, Ordering::SeqCst);
let callbacks: Vec<_> = self
.callbacks
.lock()
.unwrap_or_else(|e| e.into_inner())
.drain(..)
.collect();
for cb in callbacks {
cb();
}
}
pub fn on_cancel(&self, callback: CancelCallback) {
let mut callbacks = self.callbacks.lock().unwrap_or_else(|e| e.into_inner());
if self.is_cancelled() {
drop(callbacks);
callback();
} else {
callbacks.push(callback);
}
}
}
pub fn ctrl_c() -> impl FnOnce() + Send + 'static {
let subscribers = CTRL_C_SUBSCRIBERS.get_or_init(|| {
ctrlc::set_handler(|| {
if let Some(subs) = CTRL_C_SUBSCRIBERS.get() {
let mut guard = subs.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
guard.retain(|tx| tx.send(()).is_ok());
}
})
.expect("Ctrl+C handler already set. Use ctrl_c() instead of ctrlc::set_handler()");
Mutex::new(Vec::new())
});
let (tx, rx) = std_mpsc::channel();
subscribers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.push(tx);
move || {
let _ = rx.recv();
}
}
#[cfg(not(feature = "async"))]
pub fn block_on<F: Future>(future: F) -> F::Output {
use std::task::{Context, Poll, Wake, Waker};
struct Parker {
notified: std::sync::Mutex<bool>,
signal: std::sync::Condvar,
}
impl Parker {
fn notify(&self) {
*self.notified.lock().unwrap_or_else(|p| p.into_inner()) = true;
self.signal.notify_one();
}
fn wait(&self) {
let mut notified = self.notified.lock().unwrap_or_else(|p| p.into_inner());
while !*notified {
notified = self
.signal
.wait(notified)
.unwrap_or_else(|p| p.into_inner());
}
*notified = false;
}
}
impl Wake for Parker {
fn wake(self: std::sync::Arc<Self>) {
self.notify();
}
fn wake_by_ref(self: &std::sync::Arc<Self>) {
self.notify();
}
}
let parker = std::sync::Arc::new(Parker {
notified: std::sync::Mutex::new(false),
signal: std::sync::Condvar::new(),
});
let waker = Waker::from(parker.clone());
let mut context = Context::from_waker(&waker);
let mut future = std::pin::pin!(future);
loop {
match future.as_mut().poll(&mut context) {
Poll::Ready(value) => return value,
Poll::Pending => parker.wait(),
}
}
}
#[cfg(all(test, not(feature = "async")))]
mod parking_tests {
#[test]
fn nested_block_on_does_not_consume_the_outer_futures_wakeup() {
let (tx, rx) = std::sync::mpsc::channel();
let worker = std::thread::spawn(move || {
let mut outer_polled = false;
let value = super::block_on(std::future::poll_fn(|cx| {
if outer_polled {
return std::task::Poll::Ready(42);
}
outer_polled = true;
cx.waker().wake_by_ref();
let mut inner_polled = false;
super::block_on(std::future::poll_fn(|inner| {
if inner_polled {
std::task::Poll::Ready(())
} else {
inner_polled = true;
inner.waker().wake_by_ref();
std::task::Poll::Pending
}
}));
std::task::Poll::Pending
}));
tx.send(value).unwrap();
});
let result = rx.recv_timeout(std::time::Duration::from_secs(1));
if result.is_err() {
worker.thread().unpark();
let _ = rx.recv_timeout(std::time::Duration::from_secs(1));
}
let value = result.expect("inner executor consumed the outer wake");
worker.join().unwrap();
assert_eq!(value, 42);
}
}