telar_reactive_core/task/
mod.rs1mod pool;
18
19use std::any::Any;
20use std::cell::{Cell, Ref, RefCell, RefMut};
21use std::marker::PhantomData;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex, RwLock};
24
25use rustc_hash::FxHashMap;
26
27use crate::runtime::{SurfaceHandle, current_surface};
28
29type TaskId = u64;
30type Waker = Arc<dyn Fn() + Send + Sync>;
31
32static TASK_WAKER: RwLock<Option<Waker>> = RwLock::new(None);
35
36pub fn set_task_waker(wake: impl Fn() + Send + Sync + 'static) {
39 *TASK_WAKER.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(wake));
40}
41
42fn wake_loop() {
43 let waker = TASK_WAKER.read().unwrap_or_else(|e| e.into_inner()).clone();
44 if let Some(wake) = waker {
45 wake();
46 }
47}
48
49enum Message {
50 Item(Box<dyn Any + Send>),
51 End,
53}
54
55enum Callback {
58 Once(Box<dyn FnOnce(Box<dyn Any + Send>)>),
59 Stream {
60 item: Box<dyn FnMut(Box<dyn Any + Send>)>,
61 end: Box<dyn FnOnce()>,
62 },
63}
64
65#[derive(Default)]
68struct Mailbox {
69 posted: Mutex<Vec<(TaskId, Message)>>,
73}
74
75impl Mailbox {
76 fn post(&self, id: TaskId, message: Message) {
77 self.posted
78 .lock()
79 .unwrap_or_else(|e| e.into_inner())
80 .push((id, message));
81 }
82}
83
84struct PendingTask {
85 callback: Callback,
86 surface: SurfaceHandle,
87 cancelled: Arc<AtomicBool>,
88}
89
90#[derive(Default)]
91struct TaskRegistry {
92 next_id: TaskId,
93 pending: FxHashMap<TaskId, PendingTask>,
94 mailbox: Arc<Mailbox>,
95}
96
97struct TaskCell(Cell<*mut RefCell<TaskRegistry>>);
101
102impl TaskCell {
103 fn borrow(&self) -> Ref<'_, TaskRegistry> {
104 unsafe { (*self.0.get()).borrow() }
105 }
106 fn borrow_mut(&self) -> RefMut<'_, TaskRegistry> {
107 unsafe { (*self.0.get()).borrow_mut() }
108 }
109}
110
111thread_local! {
112 static TASKS: TaskCell = TaskCell(Cell::new(
113 Box::into_raw(Box::new(RefCell::new(TaskRegistry::default())))
114 ));
115}
116
117struct WorkerEnd {
120 mailbox: Arc<Mailbox>,
121 id: TaskId,
122 cancelled: Arc<AtomicBool>,
123}
124
125impl WorkerEnd {
126 fn post(&self, message: Message) {
127 self.mailbox.post(self.id, message);
128 wake_loop();
129 }
130}
131
132impl Drop for WorkerEnd {
133 fn drop(&mut self) {
134 self.post(Message::End);
135 }
136}
137
138pub struct Task {
144 id: TaskId,
145 cancelled: Arc<AtomicBool>,
146 _ui_thread_only: PhantomData<*const ()>,
147}
148
149impl Task {
150 pub fn cancel(self) {
154 self.cancelled.store(true, Ordering::Relaxed);
155 let dropped = TASKS.with(|t| t.borrow_mut().pending.remove(&self.id));
156 drop(dropped);
157 }
158
159 pub fn is_pending(&self) -> bool {
161 TASKS.with(|t| t.borrow().pending.contains_key(&self.id))
162 }
163}
164
165fn register(surface: SurfaceHandle, callback: Callback) -> (TaskId, Arc<Mailbox>, Arc<AtomicBool>) {
166 TASKS.with(|t| {
167 let mut registry = t.borrow_mut();
168 let id = registry.next_id;
169 registry.next_id += 1;
170 let cancelled = Arc::new(AtomicBool::new(false));
171 registry.pending.insert(
172 id,
173 PendingTask {
174 callback,
175 surface,
176 cancelled: Arc::clone(&cancelled),
177 },
178 );
179 (id, Arc::clone(®istry.mailbox), cancelled)
180 })
181}
182
183pub fn spawn_task<T, W, F>(work: W, on_done: F) -> Task
200where
201 T: Send + 'static,
202 W: FnOnce() -> T + Send + 'static,
203 F: FnOnce(T) + 'static,
204{
205 let callback = Callback::Once(Box::new(move |value| {
206 if let Ok(value) = value.downcast::<T>() {
207 on_done(*value);
208 }
209 }));
210 let (id, mailbox, cancelled) = register(current_surface(), callback);
211
212 let end = WorkerEnd {
213 mailbox,
214 id,
215 cancelled: Arc::clone(&cancelled),
216 };
217 pool::submit(Box::new(move || {
218 let value = work();
219 end.post(Message::Item(Box::new(value)));
220 }));
221
222 Task {
223 id,
224 cancelled,
225 _ui_thread_only: PhantomData,
226 }
227}
228
229pub struct Emitter<T> {
232 end: Arc<WorkerEnd>,
233 _item: PhantomData<fn(T)>,
234}
235
236impl<T: Send + 'static> Emitter<T> {
237 pub fn emit(&self, item: T) {
239 if self.is_cancelled() {
240 return;
241 }
242 self.end.post(Message::Item(Box::new(item)));
243 }
244
245 pub fn is_cancelled(&self) -> bool {
248 self.end.cancelled.load(Ordering::Relaxed)
249 }
250}
251
252impl<T> Clone for Emitter<T> {
253 fn clone(&self) -> Self {
254 Self {
255 end: Arc::clone(&self.end),
256 _item: PhantomData,
257 }
258 }
259}
260
261pub fn spawn_stream<T, W, F, E>(work: W, mut on_item: F, on_end: E) -> Task
282where
283 T: Send + 'static,
284 W: FnOnce(Emitter<T>) + Send + 'static,
285 F: FnMut(T) + 'static,
286 E: FnOnce() + 'static,
287{
288 let callback = Callback::Stream {
289 item: Box::new(move |value| {
290 if let Ok(value) = value.downcast::<T>() {
291 on_item(*value);
292 }
293 }),
294 end: Box::new(on_end),
295 };
296 let (id, mailbox, cancelled) = register(current_surface(), callback);
297
298 let emitter = Emitter::<T> {
299 end: Arc::new(WorkerEnd {
300 mailbox,
301 id,
302 cancelled: Arc::clone(&cancelled),
303 }),
304 _item: PhantomData,
305 };
306 pool::submit(Box::new(move || work(emitter)));
307
308 Task {
309 id,
310 cancelled,
311 _ui_thread_only: PhantomData,
312 }
313}
314
315pub fn drain_tasks() {
320 let posted = TASKS.with(|t| {
321 let registry = t.borrow();
322 let mut posted = registry
323 .mailbox
324 .posted
325 .lock()
326 .unwrap_or_else(|e| e.into_inner());
327 std::mem::take(&mut *posted)
328 });
329 if posted.is_empty() {
330 return;
331 }
332
333 crate::batch(|| {
334 for (id, message) in posted {
335 let Some(task) = TASKS.with(|t| t.borrow_mut().pending.remove(&id)) else {
338 continue; };
340 let value = match message {
341 Message::Item(value) => value,
342 Message::End => {
343 if let Callback::Stream { end, .. } = task.callback {
344 let _surface = task.surface.enter();
345 end();
346 }
347 continue;
348 }
349 };
350 let still_open = {
351 let _surface = task.surface.enter();
352 match task.callback {
353 Callback::Once(run) => {
354 run(value);
355 None
356 }
357 Callback::Stream { mut item, end } => {
358 item(value);
359 Some(Callback::Stream { item, end })
360 }
361 }
362 };
363 if let Some(callback) = still_open {
364 TASKS.with(|t| {
365 t.borrow_mut().pending.insert(
366 id,
367 PendingTask {
368 callback,
369 surface: task.surface,
370 cancelled: task.cancelled,
371 },
372 )
373 });
374 }
375 }
376 });
377}
378
379pub fn cancel_tasks_for(surface: SurfaceHandle) {
383 let cancelled: Vec<PendingTask> = TASKS.with(|t| {
386 let mut registry = t.borrow_mut();
387 let ids: Vec<TaskId> = registry
388 .pending
389 .iter()
390 .filter(|(_, task)| task.surface == surface)
391 .map(|(id, _)| *id)
392 .collect();
393 ids.iter()
394 .filter_map(|id| registry.pending.remove(id))
395 .inspect(|task| task.cancelled.store(true, Ordering::Relaxed))
396 .collect()
397 });
398 drop(cancelled);
399}
400
401pub fn reset_tasks() {
406 let flags: Vec<Arc<AtomicBool>> = TASKS.with(|t| {
407 t.borrow()
408 .pending
409 .values()
410 .map(|task| Arc::clone(&task.cancelled))
411 .collect()
412 });
413 for flag in flags {
414 flag.store(true, Ordering::Relaxed);
415 }
416 pool::shutdown_and_join();
418
419 let (pending, mailbox) = TASKS.with(|t| {
420 let mut registry = t.borrow_mut();
421 (
422 std::mem::take(&mut registry.pending),
423 std::mem::take(&mut registry.mailbox),
424 )
425 });
426 drop(pending);
427 drop(mailbox);
428}
429
430#[cfg(test)]
432fn pending_task_count() -> usize {
433 TASKS.with(|t| t.borrow().pending.len())
434}
435
436#[cfg(test)]
437mod tests {
438 use std::rc::Rc;
439 use std::sync::atomic::AtomicUsize;
440 use std::time::{Duration, Instant};
441
442 use super::*;
443 use crate::runtime::{SurfaceEnterGuard, set_current_surface, set_surface_enter_hook};
444
445 static SERIAL: Mutex<()> = Mutex::new(());
448
449 fn serial() -> std::sync::MutexGuard<'static, ()> {
450 SERIAL.lock().unwrap_or_else(|e| e.into_inner())
451 }
452
453 fn drain_until(done: impl Fn() -> bool) {
454 let deadline = Instant::now() + Duration::from_secs(5);
455 while !done() {
456 assert!(Instant::now() < deadline, "task never completed");
457 std::thread::sleep(Duration::from_millis(1));
458 drain_tasks();
459 }
460 }
461
462 #[test]
463 fn result_crosses_back_to_the_spawning_thread() {
464 let _serial = serial();
465 let got = Rc::new(std::cell::Cell::new(0i32));
467 let sink = Rc::clone(&got);
468 spawn_task(|| 6 * 7, move |v| sink.set(v));
469 drain_until(|| got.get() != 0);
470 assert_eq!(got.get(), 42);
471 assert_eq!(pending_task_count(), 0);
472 }
473
474 #[test]
475 fn cancel_discards_the_result() {
476 let _serial = serial();
477 let ran = Rc::new(std::cell::Cell::new(false));
478 let sink = Rc::clone(&ran);
479 let task = spawn_task(|| 1u8, move |_| sink.set(true));
480 task.cancel();
482 assert_eq!(pending_task_count(), 0);
483 std::thread::sleep(Duration::from_millis(20));
484 drain_tasks();
485 assert!(!ran.get());
486 }
487
488 #[test]
489 fn a_panicking_worker_abandons_its_callback() {
490 let _serial = serial();
491 let ran = Rc::new(std::cell::Cell::new(false));
492 let sink = Rc::clone(&ran);
493 spawn_task(
494 || -> u8 { panic!("worker blew up") },
495 move |_| sink.set(true),
496 );
497 drain_until(|| pending_task_count() == 0);
499 assert!(!ran.get());
500 }
501
502 #[test]
505 fn completion_reenters_the_spawning_surface() {
506 let _serial = serial();
507 set_surface_enter_hook(|handle| {
508 let prev = set_current_surface(handle);
509 SurfaceEnterGuard::new(move || {
510 set_current_surface(prev);
511 })
512 });
513
514 let seen = Rc::new(std::cell::Cell::new(SurfaceHandle::NONE));
515 let sink = Rc::clone(&seen);
516 {
517 let _surface_a = SurfaceHandle(7).enter();
518 spawn_task(|| (), move |()| sink.set(current_surface()));
519 }
520
521 let _surface_b = SurfaceHandle(9).enter();
522 drain_until(|| !seen.get().is_none());
523 assert_eq!(seen.get(), SurfaceHandle(7));
524 }
525
526 #[test]
527 fn a_finishing_worker_wakes_the_loop() {
528 let _serial = serial();
529 static WAKES: AtomicUsize = AtomicUsize::new(0);
530 WAKES.store(0, Ordering::SeqCst);
531 set_task_waker(|| {
532 WAKES.fetch_add(1, Ordering::SeqCst);
533 });
534
535 spawn_task(|| (), |()| {});
536 drain_until(|| pending_task_count() == 0);
537 assert!(WAKES.load(Ordering::SeqCst) >= 1);
538 }
539
540 #[test]
541 fn a_stream_delivers_every_item_in_order_then_closes() {
542 let _serial = serial();
543 let seen: Rc<RefCell<Vec<u32>>> = Rc::new(RefCell::new(Vec::new()));
544 let sink = Rc::clone(&seen);
545 let closed = Rc::new(std::cell::Cell::new(false));
546 let closed_sink = Rc::clone(&closed);
547 spawn_stream(
548 |out| {
549 for i in 0..8u32 {
550 out.emit(i);
551 }
552 },
553 move |i| sink.borrow_mut().push(i),
554 move || closed_sink.set(true),
555 );
556 drain_until(|| pending_task_count() == 0);
558 assert_eq!(*seen.borrow(), (0..8).collect::<Vec<u32>>());
559 assert!(closed.get(), "on_end must run once the worker returns");
560 }
561
562 #[test]
563 fn cancelling_a_stream_stops_its_worker() {
564 let _serial = serial();
565 let seen = Rc::new(std::cell::Cell::new(0u32));
566 let sink = Rc::clone(&seen);
567 let (started_tx, started_rx) = std::sync::mpsc::channel();
568 static EMITTED: AtomicUsize = AtomicUsize::new(0);
569 EMITTED.store(0, Ordering::SeqCst);
570
571 let task = spawn_stream(
572 move |out| {
573 started_tx.send(()).ok();
574 while !out.is_cancelled() {
576 out.emit(1u32);
577 EMITTED.fetch_add(1, Ordering::SeqCst);
578 std::thread::sleep(Duration::from_millis(1));
579 }
580 },
581 move |i| sink.set(sink.get() + i),
582 || unreachable!("on_end must not run for a cancelled stream"),
583 );
584
585 started_rx.recv().expect("worker never started");
586 task.cancel();
587 assert_eq!(
588 pending_task_count(),
589 0,
590 "cancel releases the callback at once"
591 );
592
593 std::thread::sleep(Duration::from_millis(60));
595 let settled = EMITTED.load(Ordering::SeqCst);
596 std::thread::sleep(Duration::from_millis(60));
597 assert_eq!(
598 settled,
599 EMITTED.load(Ordering::SeqCst),
600 "cancel must stop the stream worker, not just mute it"
601 );
602 drain_tasks();
603 assert_eq!(seen.get(), 0, "no item may be delivered after cancel");
604 }
605
606 #[test]
609 fn the_pool_reuses_an_idle_thread() {
610 let _serial = serial();
611 let threads: Rc<RefCell<Vec<std::thread::ThreadId>>> = Rc::new(RefCell::new(Vec::new()));
612 for _ in 0..2 {
613 let sink = Rc::clone(&threads);
614 spawn_task(
615 || std::thread::current().id(),
616 move |id| sink.borrow_mut().push(id),
617 );
618 drain_until(|| pending_task_count() == 0);
619 }
620 let threads = threads.borrow();
621 assert_eq!(threads.len(), 2);
622 assert_eq!(
623 threads[0], threads[1],
624 "the second task should have reused the idle worker"
625 );
626 }
627}