Skip to main content

sequoia_async/
runtime.rs

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}