1#![forbid(unsafe_code)]
2
3use std::any::Any;
4use std::cell::{Cell, RefCell};
5use std::collections::HashMap;
6use std::panic::{AssertUnwindSafe, catch_unwind};
7use std::rc::Rc;
8use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
9use std::sync::{Arc, Mutex};
10use std::time::Duration;
11
12use futures_core::Future;
13use sequoia_reactive::Signal;
14
15use crate::emitter::{Emitter, StreamKind, StreamMsgAny};
16use crate::task::{TaskHandle, TaskId};
17use crate::wake::{WakeFn, noop_wake};
18
19enum Completion {
20 Done(u64, Box<dyn Any + Send>),
21 Panicked(u64),
22}
23
24type Apply = Box<dyn FnOnce(Box<dyn Any + Send>)>;
25type StreamApply = std::rc::Rc<dyn Fn(Box<dyn Any + Send>)>;
26type Job = Box<dyn FnOnce() + Send + 'static>;
27
28const THREAD_NAME: &str = "sequoia-async";
29
30#[derive(Clone)]
31pub struct AsyncRuntime {
32 inner: Rc<Inner>,
33}
34
35struct Inner {
36 pool: Arc<Pool>,
37 done: Receiver<Completion>,
38 applies: RefCell<HashMap<u64, Apply>>,
39 streams: RefCell<HashMap<u64, StreamApply>>,
40 stream_tx: Sender<StreamMsgAny>,
41 stream_rx: Receiver<StreamMsgAny>,
42 pending: Signal<u64>,
43 next_id: Cell<u64>,
44 wake: WakeFn,
45}
46
47impl AsyncRuntime {
48 pub fn new(threads: usize) -> Self {
49 Self::with_wake(threads, noop_wake())
50 }
51
52 pub fn with_default_threads() -> Self {
53 Self::new(default_threads())
54 }
55
56 pub fn with_wake(threads: usize, wake: WakeFn) -> Self {
57 let (tx, rx) = channel::<Completion>();
58 let (stx, srx) = channel::<StreamMsgAny>();
59 Self {
60 inner: Rc::new(Inner {
61 pool: Pool::new(threads, tx),
62 done: rx,
63 applies: RefCell::new(HashMap::new()),
64 streams: RefCell::new(HashMap::new()),
65 stream_tx: stx,
66 stream_rx: srx,
67 pending: Signal::new(0),
68 next_id: Cell::new(1),
69 wake,
70 }),
71 }
72 }
73
74 fn reserve(&self) -> u64 {
75 let id = self.inner.next_id.get();
76 self.inner.next_id.set(id + 1);
77 self.inner.pending.update(|n| *n += 1);
78 id
79 }
80
81 pub fn spawn_future<T, F, A>(&self, future: F, apply: A) -> TaskHandle
82 where
83 T: Send + 'static,
84 F: Future<Output = T> + Send + 'static,
85 A: FnOnce(T) + 'static,
86 {
87 let id = self.reserve();
88 self.inner.applies.borrow_mut().insert(
89 id,
90 Box::new(move |any: Box<dyn Any + Send>| {
91 if let Ok(value) = any.downcast::<T>() {
92 apply(*value);
93 }
94 }),
95 );
96 let tx = self.inner.pool.results_tx();
97 let wake = self.inner.wake.clone();
98 self.inner.pool.submit(Box::new(move || {
99 let msg = match catch_unwind(AssertUnwindSafe(|| pollster::block_on(future))) {
100 Ok(value) => Completion::Done(id, Box::new(value)),
101 Err(_) => Completion::Panicked(id),
102 };
103 let _ = tx.send(msg);
104 wake();
105 }));
106 let rt = self.clone();
107 TaskHandle::new(TaskId::new(id), move |tid| rt.cancel_raw(tid.get()))
108 }
109
110 pub fn spawn_task<F>(&self, future: F) -> TaskHandle
111 where
112 F: Future<Output = ()> + Send + 'static,
113 {
114 self.spawn_future(future, |()| {})
115 }
116
117 pub fn spawn_blocking<T, W, A>(&self, work: W, apply: A) -> TaskHandle
118 where
119 T: Send + 'static,
120 W: FnOnce() -> T + Send + 'static,
121 A: FnOnce(T) + 'static,
122 {
123 let id = self.reserve();
124 self.inner.applies.borrow_mut().insert(
125 id,
126 Box::new(move |any: Box<dyn Any + Send>| {
127 if let Ok(value) = any.downcast::<T>() {
128 apply(*value);
129 }
130 }),
131 );
132 let tx = self.inner.pool.results_tx();
133 let wake = self.inner.wake.clone();
134 self.inner.pool.submit(Box::new(move || {
135 let msg = match catch_unwind(AssertUnwindSafe(work)) {
136 Ok(value) => Completion::Done(id, Box::new(value)),
137 Err(_) => Completion::Panicked(id),
138 };
139 let _ = tx.send(msg);
140 wake();
141 }));
142 let rt = self.clone();
143 TaskHandle::new(TaskId::new(id), move |tid| rt.cancel_raw(tid.get()))
144 }
145
146 fn cancel_raw(&self, id: u64) -> bool {
147 let mut hit = false;
148 if self.inner.applies.borrow_mut().remove(&id).is_some() {
149 hit = true;
150 }
151 if self.inner.streams.borrow_mut().remove(&id).is_some() {
152 hit = true;
153 }
154 if hit {
155 self.inner.pending.update(|n| *n = n.saturating_sub(1));
156 }
157 hit
158 }
159
160 pub fn cancel(&self, id: TaskId) -> bool {
161 self.cancel_raw(id.get())
162 }
163
164 pub fn spawn_stream<T, F, Make, C>(&self, make_future: Make, on_item: C) -> TaskHandle
165 where
166 T: Send + 'static,
167 F: Future<Output = ()> + Send + 'static,
168 Make: FnOnce(Emitter<T>) -> F + Send + 'static,
169 C: Fn(T) + 'static,
170 {
171 let id = self.reserve();
172 let apply: StreamApply = std::rc::Rc::new(move |any: Box<dyn Any + Send>| {
173 if let Ok(value) = any.downcast::<T>() {
174 on_item(*value);
175 }
176 });
177 self.inner.streams.borrow_mut().insert(id, apply);
178 let stx = self.inner.stream_tx.clone();
179 let wake = self.inner.wake.clone();
180 let emitter = Emitter::new(id, stx.clone(), wake.clone());
181 let future = make_future(emitter);
182 self.inner.pool.submit(Box::new(move || {
183 let outcome = catch_unwind(AssertUnwindSafe(|| pollster::block_on(future)));
184 let kind = match outcome {
185 Ok(()) => StreamKind::Finished,
186 Err(_) => StreamKind::Panicked,
187 };
188 let _ = stx.send(StreamMsgAny { id, kind });
189 wake();
190 }));
191 let rt = self.clone();
192 TaskHandle::new(TaskId::new(id), move |tid| rt.cancel_raw(tid.get()))
193 }
194
195 pub fn spawn_to_signal<T, F>(&self, signal: &Signal<T>, future: F) -> TaskHandle
196 where
197 T: Clone + PartialEq + Send + 'static,
198 F: Future<Output = T> + Send + 'static,
199 {
200 let signal = signal.clone();
201 self.spawn_future(future, move |v| signal.set(v))
202 }
203
204 pub fn pending(&self) -> Signal<u64> {
205 self.inner.pending.clone()
206 }
207
208 pub fn is_busy(&self) -> bool {
209 self.inner.pending.get_untracked() > 0
210 }
211
212 pub fn threads(&self) -> usize {
213 self.inner.pool.threads()
214 }
215
216 pub fn drain(&self) -> usize {
217 let mut handled = 0;
218 loop {
219 let msg = match self.inner.done.try_recv() {
220 Ok(msg) => msg,
221 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
222 };
223 let id = match &msg {
224 Completion::Done(id, _) | Completion::Panicked(id) => *id,
225 };
226 let apply = self.inner.applies.borrow_mut().remove(&id);
227 match (msg, apply) {
228 (Completion::Done(_, value), Some(apply)) => {
229 apply(value);
230 handled += 1;
231 self.inner.pending.update(|n| *n = n.saturating_sub(1));
232 }
233 (Completion::Panicked(..), Some(_)) => {
234 log::error!("sequoia-async: task #{id} panicked — result dropped");
235 handled += 1;
236 self.inner.pending.update(|n| *n = n.saturating_sub(1));
237 }
238 (Completion::Done(..), None) | (Completion::Panicked(..), None) => {}
239 }
240 }
241 handled += self.drain_streams();
242 handled
243 }
244
245 fn drain_streams(&self) -> usize {
246 let mut handled = 0;
247 loop {
248 let msg = match self.inner.stream_rx.try_recv() {
249 Ok(msg) => msg,
250 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
251 };
252 match msg.kind {
253 StreamKind::Item(value) => {
254 if let Some(apply) = self.inner.streams.borrow().get(&msg.id).cloned() {
255 apply(value);
256 handled += 1;
257 }
258 }
259 StreamKind::Finished | StreamKind::Panicked => {
260 let was = self.inner.streams.borrow_mut().remove(&msg.id).is_some();
261 if was {
262 if matches!(msg.kind, StreamKind::Panicked) {
263 log::error!("sequoia-async: stream #{} panicked — closed", msg.id);
264 }
265 self.inner.pending.update(|n| *n = n.saturating_sub(1));
266 handled += 1;
267 }
268 }
269 }
270 }
271 handled
272 }
273
274 pub fn drain_until(&self, timeout: Duration) -> usize {
275 crate::testing::drain_until(self, timeout)
276 }
277}
278
279struct Pool {
280 jobs: Mutex<Option<Sender<Job>>>,
281 results: Mutex<Sender<Completion>>,
282 threads: usize,
283}
284
285impl Pool {
286 fn new(threads: usize, results_tx: Sender<Completion>) -> Arc<Self> {
287 let threads = threads.max(1);
288 let (jobs_tx, jobs_rx) = channel::<Job>();
289 let jobs_rx = Arc::new(Mutex::new(jobs_rx));
290 let mut started = 0;
291 for index in 0..threads {
292 let rx = Arc::clone(&jobs_rx);
293 let spawned = std::thread::Builder::new()
294 .name(format!("{THREAD_NAME}-{index}"))
295 .spawn(move || {
296 loop {
297 let job = rx.lock().unwrap_or_else(|e| e.into_inner()).recv();
298 match job {
299 Ok(job) => job(),
300 Err(_) => break,
301 }
302 }
303 });
304 if spawned.is_ok() {
305 started += 1;
306 } else {
307 log::error!("sequoia-async: failed to start worker thread");
308 }
309 }
310 Arc::new(Self {
311 jobs: Mutex::new(Some(jobs_tx)),
312 results: Mutex::new(results_tx),
313 threads: started.max(1),
314 })
315 }
316
317 fn submit(&self, job: Job) {
318 let guard = self.jobs.lock().unwrap_or_else(|e| e.into_inner());
319 if let Some(tx) = guard.as_ref()
320 && tx.send(job).is_err()
321 {
322 log::error!("sequoia-async: failed to queue task");
323 }
324 }
325
326 fn results_tx(&self) -> Sender<Completion> {
327 self.results
328 .lock()
329 .unwrap_or_else(|e| e.into_inner())
330 .clone()
331 }
332
333 fn threads(&self) -> usize {
334 self.threads
335 }
336}
337
338impl Drop for Pool {
339 fn drop(&mut self) {
340 self.jobs.lock().unwrap_or_else(|e| e.into_inner()).take();
341 }
342}
343
344pub(crate) fn default_threads() -> usize {
345 std::thread::available_parallelism()
346 .map(|n| n.get().saturating_sub(1).max(1))
347 .unwrap_or(1)
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn future_runs_off_thread_and_apply_on_caller() {
356 use std::sync::{Arc, Mutex};
357 let rt = AsyncRuntime::new(1);
358 let ui = std::thread::current().id();
359 let worker = Arc::new(Mutex::new(None));
360 let out = Arc::new(Mutex::new(None));
361 let w = Arc::clone(&worker);
362 let o = Arc::clone(&out);
363 rt.spawn_future(
364 async move {
365 w.lock().unwrap().replace(std::thread::current().id());
366 21 * 2
367 },
368 move |v: i32| {
369 o.lock().unwrap().replace((v, std::thread::current().id()));
370 },
371 );
372 assert!(rt.is_busy());
373 assert_eq!(rt.drain_until(Duration::from_secs(5)), 1);
374 assert_eq!(*out.lock().unwrap(), Some((42, ui)));
375 assert_ne!(worker.lock().unwrap().unwrap(), ui);
376 assert!(!rt.is_busy());
377 }
378
379 #[test]
380 fn cancel_drops_result() {
381 use std::sync::Arc;
382 use std::sync::atomic::{AtomicBool, Ordering};
383 let rt = AsyncRuntime::new(1);
384 let flag = Arc::new(AtomicBool::new(false));
385 let f = Arc::clone(&flag);
386 let h = rt.spawn_future(
387 async move {
388 crate::time::sleep(Duration::from_millis(30)).await;
389 },
390 move |()| f.store(true, Ordering::SeqCst),
391 );
392 assert!(h.cancel());
393 std::thread::sleep(Duration::from_millis(120));
394 assert_eq!(rt.drain(), 0);
395 assert!(!flag.load(Ordering::SeqCst));
396 assert_eq!(rt.pending().get_untracked(), 0);
397 }
398
399 #[test]
400 fn threads_never_below_one() {
401 let rt = AsyncRuntime::new(0);
402 assert_eq!(rt.threads(), 1);
403 assert_eq!(rt.drain(), 0);
404 }
405
406 #[test]
407 fn stream_delivers_multiple_items_then_finishes() {
408 use std::sync::{Arc, Mutex};
409 let rt = AsyncRuntime::new(2);
410 let items = Arc::new(Mutex::new(Vec::new()));
411 let probe = Arc::clone(&items);
412 let _h = rt.spawn_stream(
413 |em: Emitter<i32>| async move {
414 for v in [1, 2, 3] {
415 em.emit(v).unwrap();
416 }
417 },
418 move |v: i32| probe.lock().unwrap().push(v),
419 );
420 assert!(rt.is_busy());
421 let ok = crate::testing::run_until(&rt, Duration::from_secs(5), || {
422 items.lock().unwrap().len() == 3 && !rt.is_busy()
423 });
424 assert!(ok, "stream items + finish drained");
425 assert_eq!(*items.lock().unwrap(), vec![1, 2, 3]);
426 assert_eq!(rt.pending().get_untracked(), 0);
427 }
428
429 #[test]
430 fn stream_cancel_stops_delivery() {
431 use std::sync::{Arc, Mutex};
432 let rt = AsyncRuntime::new(1);
433 let items = Arc::new(Mutex::new(Vec::new()));
434 let probe = Arc::clone(&items);
435 let h = rt.spawn_stream(
436 |em: Emitter<i32>| async move {
437 crate::time::sleep(Duration::from_millis(50)).await;
438 let _ = em.emit(99);
439 },
440 move |v: i32| probe.lock().unwrap().push(v),
441 );
442 assert!(h.cancel());
443 std::thread::sleep(Duration::from_millis(150));
444 rt.drain();
445 assert!(items.lock().unwrap().is_empty());
446 assert_eq!(rt.pending().get_untracked(), 0);
447 }
448
449 #[test]
450 fn spawn_to_signal_writes_value() {
451 let rt = AsyncRuntime::new(1);
452 let s = Signal::new(0u32);
453 rt.spawn_to_signal(&s, async { 11u32 });
454 assert!(crate::testing::run_until(
455 &rt,
456 Duration::from_secs(5),
457 || { s.get_untracked() == 11 }
458 ));
459 }
460}