spawned_rt/threads/
mod.rs1pub mod mpsc;
4pub mod oneshot;
5
6#[cfg(spawned_loom)]
9use loom::sync::{
10 atomic::{AtomicBool, Ordering},
11 Arc, Mutex,
12};
13#[cfg(not(spawned_loom))]
14use std::sync::{
15 atomic::{AtomicBool, Ordering},
16 Arc, Mutex,
17};
18use std::sync::{mpsc as std_mpsc, OnceLock};
19pub use std::{
20 future::Future,
21 thread::{sleep, spawn, JoinHandle},
22};
23
24#[cfg(all(feature = "async", not(spawned_sim)))]
25use crate::tasks::Runtime;
26use crate::tracing::init_tracing;
28#[cfg(all(feature = "async", spawned_sim))]
29use tokio::runtime::Runtime;
30
31static CTRL_C_SUBSCRIBERS: OnceLock<Mutex<Vec<std_mpsc::Sender<()>>>> = OnceLock::new();
33
34pub fn run(f: fn()) {
36 init_tracing();
37
38 f()
39}
40
41#[cfg(feature = "async")]
48pub fn block_on<F: Future>(future: F) -> F::Output {
49 #[cfg(feature = "backend-experiments")]
50 if std::env::var("SPAWNED_EXPERIMENT_RUNTIME").as_deref() == Ok("managed") {
51 static POOL: OnceLock<Runtime> = OnceLock::new();
53 return POOL
54 .get_or_init(|| {
55 tokio::runtime::Builder::new_multi_thread()
56 .worker_threads(2)
57 .enable_all()
58 .build()
59 .unwrap()
60 })
61 .block_on(future);
62 }
63 #[cfg(feature = "backend-experiments")]
64 let rt = match std::env::var("SPAWNED_EXPERIMENT_RUNTIME").as_deref() {
65 Ok("current") => tokio::runtime::Builder::new_current_thread()
66 .enable_all()
67 .build()
68 .unwrap(),
69 Ok("one-worker") => tokio::runtime::Builder::new_multi_thread()
70 .worker_threads(1)
71 .enable_all()
72 .build()
73 .unwrap(),
74 _ => isolated_runtime(),
75 };
76 #[cfg(not(feature = "backend-experiments"))]
77 let rt = isolated_runtime();
78 rt.block_on(future)
79}
80
81#[cfg(feature = "async")]
84fn isolated_runtime() -> Runtime {
85 tokio::runtime::Builder::new_multi_thread()
86 .worker_threads(1)
87 .enable_all()
88 .build()
89 .unwrap()
90}
91
92pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
94where
95 F: FnOnce() -> R + Send + 'static,
96 R: Send + 'static,
97{
98 spawn(f)
99}
100
101type CancelCallback = Box<dyn FnOnce() + Send>;
102
103#[derive(Clone)]
108pub struct CancellationToken {
109 is_cancelled: Arc<AtomicBool>,
110 callbacks: Arc<Mutex<Vec<CancelCallback>>>,
111}
112
113impl std::fmt::Debug for CancellationToken {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("CancellationToken")
116 .field("is_cancelled", &self.is_cancelled())
117 .finish()
118 }
119}
120
121impl Default for CancellationToken {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl CancellationToken {
128 pub fn new() -> Self {
129 CancellationToken {
130 is_cancelled: Arc::new(false.into()),
131 callbacks: Arc::new(Mutex::new(Vec::new())),
132 }
133 }
134
135 pub fn is_cancelled(&self) -> bool {
136 self.is_cancelled.load(Ordering::SeqCst)
137 }
138
139 pub fn cancel(&self) {
140 self.is_cancelled.store(true, Ordering::SeqCst);
141 let callbacks: Vec<_> = self
143 .callbacks
144 .lock()
145 .unwrap_or_else(|e| e.into_inner())
146 .drain(..)
147 .collect();
148 for cb in callbacks {
149 cb();
150 }
151 }
152
153 pub fn on_cancel(&self, callback: CancelCallback) {
159 let mut callbacks = self.callbacks.lock().unwrap_or_else(|e| e.into_inner());
164 if self.is_cancelled() {
165 drop(callbacks);
166 callback();
167 } else {
168 callbacks.push(callback);
169 }
170 }
171}
172
173pub fn ctrl_c() -> impl FnOnce() + Send + 'static {
190 let subscribers = CTRL_C_SUBSCRIBERS.get_or_init(|| {
192 ctrlc::set_handler(|| {
193 if let Some(subs) = CTRL_C_SUBSCRIBERS.get() {
194 let mut guard = subs.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
195 guard.retain(|tx| tx.send(()).is_ok());
197 }
198 })
199 .expect("Ctrl+C handler already set. Use ctrl_c() instead of ctrlc::set_handler()");
200 Mutex::new(Vec::new())
201 });
202
203 let (tx, rx) = std_mpsc::channel();
205 subscribers
206 .lock()
207 .unwrap_or_else(|poisoned| poisoned.into_inner())
208 .push(tx);
209
210 move || {
211 let _ = rx.recv();
212 }
213}
214
215#[cfg(not(feature = "async"))]
218pub fn block_on<F: Future>(future: F) -> F::Output {
219 use std::task::{Context, Poll, Wake, Waker};
220 struct Parker {
221 notified: std::sync::Mutex<bool>,
222 signal: std::sync::Condvar,
223 }
224 impl Parker {
225 fn notify(&self) {
226 *self.notified.lock().unwrap_or_else(|p| p.into_inner()) = true;
227 self.signal.notify_one();
228 }
229 fn wait(&self) {
230 let mut notified = self.notified.lock().unwrap_or_else(|p| p.into_inner());
231 while !*notified {
232 notified = self
233 .signal
234 .wait(notified)
235 .unwrap_or_else(|p| p.into_inner());
236 }
237 *notified = false;
238 }
239 }
240 impl Wake for Parker {
241 fn wake(self: std::sync::Arc<Self>) {
242 self.notify();
243 }
244 fn wake_by_ref(self: &std::sync::Arc<Self>) {
245 self.notify();
246 }
247 }
248 let parker = std::sync::Arc::new(Parker {
251 notified: std::sync::Mutex::new(false),
252 signal: std::sync::Condvar::new(),
253 });
254 let waker = Waker::from(parker.clone());
255 let mut context = Context::from_waker(&waker);
256 let mut future = std::pin::pin!(future);
257 loop {
258 match future.as_mut().poll(&mut context) {
259 Poll::Ready(value) => return value,
260 Poll::Pending => parker.wait(),
261 }
262 }
263}
264
265#[cfg(all(test, not(feature = "async")))]
266mod parking_tests {
267 #[test]
268 fn nested_block_on_does_not_consume_the_outer_futures_wakeup() {
269 let (tx, rx) = std::sync::mpsc::channel();
270 let worker = std::thread::spawn(move || {
271 let mut outer_polled = false;
272 let value = super::block_on(std::future::poll_fn(|cx| {
273 if outer_polled {
274 return std::task::Poll::Ready(42);
275 }
276 outer_polled = true;
277 cx.waker().wake_by_ref();
278 let mut inner_polled = false;
279 super::block_on(std::future::poll_fn(|inner| {
280 if inner_polled {
281 std::task::Poll::Ready(())
282 } else {
283 inner_polled = true;
284 inner.waker().wake_by_ref();
285 std::task::Poll::Pending
286 }
287 }));
288 std::task::Poll::Pending
289 }));
290 tx.send(value).unwrap();
291 });
292 let result = rx.recv_timeout(std::time::Duration::from_secs(1));
293 if result.is_err() {
294 worker.thread().unpark();
297 let _ = rx.recv_timeout(std::time::Duration::from_secs(1));
298 }
299 let value = result.expect("inner executor consumed the outer wake");
300 worker.join().unwrap();
301 assert_eq!(value, 42);
302 }
303}