1use crate::{properties::SharedPropertyValue, Interpolatable, Property};
6use std::{
7 any::Any,
8 cell::{Cell, RefCell},
9 collections::{HashMap, HashSet, VecDeque},
10 future::Future,
11 marker::PhantomData,
12 pin::Pin,
13 rc::{Rc, Weak},
14 sync::{
15 atomic::{AtomicUsize, Ordering},
16 Arc, Mutex, MutexGuard,
17 },
18 task::{Context, Poll, Waker},
19 time::Duration,
20};
21
22pub(crate) fn lock<T>(value: &Mutex<T>) -> MutexGuard<'_, T> {
23 value.lock().unwrap_or_else(|error| error.into_inner())
24}
25
26#[derive(Clone, Debug)]
28pub struct AsyncLimits {
29 pub scopes: usize,
30 pub tasks: usize,
31 pub publishers: usize,
32 pub endpoints: usize,
33 pub message_slots: usize,
34 pub channel_capacity: usize,
35 pub callbacks_per_turn: usize,
36 pub callback_time: Duration,
37}
38impl Default for AsyncLimits {
39 fn default() -> Self {
40 Self {
41 scopes: 1024,
42 tasks: 1024,
43 publishers: 1024,
44 endpoints: 1024,
45 message_slots: 4096,
46 channel_capacity: 32,
47 callbacks_per_turn: 64,
48 callback_time: Duration::from_millis(2),
49 }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum AsyncError {
56 Closed,
57 Limit(&'static str),
58 ZeroCapacity,
59}
60impl std::fmt::Display for AsyncError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 Self::Closed => f.write_str("the work lifetime is closed"),
64 Self::Limit(name) => write!(f, "application {name} limit reached"),
65 Self::ZeroCapacity => f.write_str("a UI channel needs at least one slot"),
66 }
67 }
68}
69impl std::error::Error for AsyncError {}
70
71#[derive(Debug, PartialEq, Eq)]
73pub struct Closed<T>(pub T);
74#[derive(Debug, PartialEq, Eq)]
76pub enum TrySendError<T> {
77 Full(T),
78 Closed(T),
79}
80
81type Payload = Box<dyn Any + Send>;
82type Wake = Arc<dyn Fn() + Send + Sync>;
83
84#[derive(Clone)]
87pub(crate) struct Authority(Vec<Arc<Mutex<bool>>>);
88impl Authority {
89 fn root() -> Self {
90 Self(vec![Arc::new(Mutex::new(true))])
91 }
92 fn child(&self) -> Self {
93 let mut gates = self.0.clone();
94 gates.push(Arc::new(Mutex::new(true)));
95 Self(gates)
96 }
97 pub(crate) fn acquire(&self) -> Option<Vec<MutexGuard<'_, bool>>> {
98 let mut guards = Vec::with_capacity(self.0.len());
99 for gate in &self.0 {
100 let guard = lock(gate);
101 if !*guard {
102 return None;
103 }
104 guards.push(guard);
105 }
106 Some(guards)
107 }
108 fn live(&self) -> bool {
109 self.acquire().is_some()
110 }
111 fn revoke(&self) {
112 *lock(self.0.last().unwrap()) = false;
113 }
114}
115
116#[derive(Default)]
117struct Ready {
118 order: VecDeque<u64>,
119 set: HashSet<u64>,
120}
121struct Hub {
122 ready: Mutex<Ready>,
123 wake: Mutex<Wake>,
124}
125impl Hub {
126 fn notify(&self, id: u64) {
127 let wake = {
128 let mut ready = lock(&self.ready);
129 let was_empty = ready.order.is_empty();
130 if ready.set.insert(id) {
131 ready.order.push_back(id);
132 }
133 was_empty
134 };
135 if wake {
136 self.wake();
137 }
138 }
139 fn wake(&self) {
140 let wake = lock(&self.wake).clone();
141 wake();
142 }
143 fn forget(&self, id: u64) {
144 let mut ready = lock(&self.ready);
145 ready.set.remove(&id);
146 ready.order.retain(|queued| *queued != id);
147 }
148 fn prepend(&self, ids: impl IntoIterator<Item = u64>) {
149 let ids: Vec<_> = ids.into_iter().collect();
150 let mut ready = lock(&self.ready);
151 for id in ids.into_iter().rev() {
152 ready.order.retain(|queued| *queued != id);
153 ready.set.insert(id);
154 ready.order.push_front(id);
155 }
156 }
157 fn take(&self) -> Vec<u64> {
158 let mut ready = lock(&self.ready);
159 ready.set.clear();
160 ready.order.drain(..).collect()
161 }
162}
163
164struct QueueState {
165 values: VecDeque<Payload>,
166 closed: bool,
167 producers: usize,
168 waiters: HashMap<usize, Waker>,
169 next_waiter: usize,
170}
171struct Queue {
172 id: u64,
173 capacity: usize,
174 authority: Authority,
175 state: Mutex<QueueState>,
176 hub: std::sync::Weak<Hub>,
177}
178impl Queue {
179 fn notify(&self) {
180 if lock(&self.state).closed {
181 return;
182 }
183 if let Some(hub) = self.hub.upgrade() {
184 hub.notify(self.id);
185 }
186 }
187 fn send(&self, value: Payload) -> Result<(), TrySendError<Payload>> {
188 let Some(authority) = self.authority.acquire() else {
189 return Err(TrySendError::Closed(value));
190 };
191 let mut state = lock(&self.state);
192 if state.closed {
193 return Err(TrySendError::Closed(value));
194 }
195 if state.values.len() == self.capacity {
196 return Err(TrySendError::Full(value));
197 }
198 state.values.push_back(value);
199 drop(state);
200 drop(authority);
201 self.notify();
202 Ok(())
203 }
204 fn pop(&self) -> Option<Payload> {
205 let (value, waiters) = {
206 let mut state = lock(&self.state);
207 (state.values.pop_front(), std::mem::take(&mut state.waiters))
208 };
209 for waiter in waiters.into_values() {
210 waiter.wake();
211 }
212 value
213 }
214 fn close(&self) {
215 let (values, waiters) = {
216 let mut state = lock(&self.state);
217 state.closed = true;
218 (
219 std::mem::take(&mut state.values),
220 std::mem::take(&mut state.waiters),
221 )
222 };
223 drop(values);
224 for waiter in waiters.into_values() {
225 waiter.wake();
226 }
227 }
228 fn finished(&self) -> bool {
229 let state = lock(&self.state);
230 state.closed || (state.producers == 0 && state.values.is_empty())
231 }
232}
233
234pub struct UiSender<T> {
236 queue: Arc<Queue>,
237 _payload: PhantomData<Mutex<T>>,
238}
239impl<T> Clone for UiSender<T> {
240 fn clone(&self) -> Self {
241 lock(&self.queue.state).producers += 1;
242 Self {
243 queue: self.queue.clone(),
244 _payload: PhantomData,
245 }
246 }
247}
248impl<T> Drop for UiSender<T> {
249 fn drop(&mut self) {
250 let last = {
251 let mut state = lock(&self.queue.state);
252 state.producers -= 1;
253 state.producers == 0
254 };
255 if last {
256 self.queue.notify();
257 }
258 }
259}
260impl<T: Send + 'static> UiSender<T> {
261 pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> {
263 self.queue
264 .send(Box::new(value))
265 .map_err(|error| match error {
266 TrySendError::Full(value) => TrySendError::Full(*value.downcast().unwrap()),
267 TrySendError::Closed(value) => TrySendError::Closed(*value.downcast().unwrap()),
268 })
269 }
270 pub fn send(&self, value: T) -> SendFuture<'_, T> {
272 SendFuture {
273 sender: self,
274 value: Some(value),
275 waiter: None,
276 }
277 }
278}
279
280pub struct SendFuture<'a, T> {
282 sender: &'a UiSender<T>,
283 value: Option<T>,
284 waiter: Option<usize>,
285}
286impl<T> Unpin for SendFuture<'_, T> {}
287impl<T> Drop for SendFuture<'_, T> {
288 fn drop(&mut self) {
289 if let Some(id) = self.waiter {
290 let removed = lock(&self.sender.queue.state).waiters.remove(&id);
291 drop(removed);
292 }
293 }
294}
295impl<T: Send + 'static> Future for SendFuture<'_, T> {
296 type Output = Result<(), Closed<T>>;
297 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
298 let queue = self.sender.queue.clone();
299 let waker = cx.waker().clone();
300 let Some(authority) = queue.authority.acquire() else {
301 return Poll::Ready(Err(Closed(
302 self.value.take().expect("send polled after completion"),
303 )));
304 };
305 let mut state = lock(&queue.state);
306 if state.closed {
307 return Poll::Ready(Err(Closed(
308 self.value.take().expect("send polled after completion"),
309 )));
310 }
311 if state.values.len() < queue.capacity {
312 let previous = self.waiter.take().and_then(|id| state.waiters.remove(&id));
313 state.values.push_back(Box::new(
314 self.value.take().expect("send polled after completion"),
315 ));
316 drop(state);
317 drop(authority);
318 drop(previous);
319 queue.notify();
320 return Poll::Ready(Ok(()));
321 }
322 let id = *self.waiter.get_or_insert_with(|| {
323 state.next_waiter += 1;
324 state.next_waiter
325 });
326 let previous = state.waiters.insert(id, waker);
327 drop(state);
328 drop(authority);
329 drop(previous);
330 Poll::Pending
331 }
332}
333
334pub struct Completion<T>(UiSender<T>);
336impl<T: Send + 'static> Completion<T> {
337 pub fn complete(self, value: T) -> Result<(), Closed<T>> {
338 self.0.try_send(value).map_err(|error| match error {
339 TrySendError::Closed(value) | TrySendError::Full(value) => Closed(value),
340 })
341 }
342}
343
344pub struct UiSubscription {
346 owner: Weak<DispatcherInner>,
347 id: u64,
348}
349impl Drop for UiSubscription {
350 fn drop(&mut self) {
351 if let Some(owner) = self.owner.upgrade() {
352 owner.remove_endpoint(self.id);
353 }
354 }
355}
356
357struct PublisherLease {
358 count: Arc<AtomicUsize>,
359}
360impl Drop for PublisherLease {
361 fn drop(&mut self) {
362 self.count.fetch_sub(1, Ordering::Relaxed);
363 }
364}
365#[derive(Clone)]
369pub struct GuardedPublisher<T> {
370 property: Property<T>,
371 authority: Authority,
372 _lease: Arc<PublisherLease>,
373}
374impl<T: SharedPropertyValue> GuardedPublisher<T> {
375 pub fn set(&self, value: T) -> Result<(), Closed<T>> {
376 self.property.publish_guarded(value, &self.authority)
377 }
378 pub fn is_closed(&self) -> bool {
379 !self.authority.live()
380 }
381}
382
383type CallbackContext = Rc<dyn Fn(&mut dyn FnMut())>;
384struct ScopeInner {
385 id: u64,
386 owner: Weak<DispatcherInner>,
387 authority: Authority,
388 callback_context: RefCell<Option<CallbackContext>>,
389 _parent: Option<AsyncScope>,
390}
391impl Drop for ScopeInner {
392 fn drop(&mut self) {
393 self.authority.revoke();
394 if let Some(owner) = self.owner.upgrade() {
395 owner.scopes.borrow_mut().remove(&self.id);
396 }
397 }
398}
399#[derive(Clone)]
410pub struct AsyncScope(Rc<ScopeInner>);
411impl AsyncScope {
412 fn owner(&self) -> Result<Rc<DispatcherInner>, AsyncError> {
413 if !self.0.authority.live() {
414 return Err(AsyncError::Closed);
415 }
416 self.0.owner.upgrade().ok_or(AsyncError::Closed)
417 }
418 #[doc(hidden)]
420 pub fn set_callback_context(&self, enter: impl Fn(&mut dyn FnMut()) + 'static) {
421 *self.0.callback_context.borrow_mut() = Some(Rc::new(enter));
422 }
423 pub fn id(&self) -> u64 {
424 self.0.id
425 }
426 pub fn is_closed(&self) -> bool {
427 !self.0.authority.live() || self.0.owner.strong_count() == 0
428 }
429 pub fn child(&self) -> Result<Self, AsyncError> {
431 let owner = self.owner()?;
432 owner.scope(Some(self.clone()))
433 }
434 pub fn cancel(&self) {
435 self.0.authority.revoke();
436 if let Some(owner) = self.0.owner.upgrade() {
437 owner.sweep_cancelled();
438 }
439 }
440 pub fn replace<T: SharedPropertyValue>(
443 &self,
444 previous: &mut Option<AsyncScope>,
445 property: &Property<T>,
446 initial: T,
447 ) -> Result<AsyncScope, AsyncError> {
448 if let Some(old) = previous.take() {
449 old.cancel();
450 }
451 self.owner()?;
452 property.set(initial);
453 let next = self.child()?;
454 *previous = Some(next.clone());
455 Ok(next)
456 }
457 pub fn publisher<T: SharedPropertyValue>(
458 &self,
459 property: &Property<T>,
460 ) -> Result<GuardedPublisher<T>, AsyncError> {
461 let owner = self.owner()?;
462 owner
463 .publishers
464 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
465 (count < owner.limits.publishers).then_some(count + 1)
466 })
467 .map_err(|_| AsyncError::Limit("publisher"))?;
468 Ok(GuardedPublisher {
469 property: property.clone(),
470 authority: self.0.authority.clone(),
471 _lease: Arc::new(PublisherLease {
472 count: owner.publishers.clone(),
473 }),
474 })
475 }
476 pub fn completion<T: Send + 'static>(
477 &self,
478 callback: impl FnOnce(T) + 'static,
479 ) -> Result<Completion<T>, AsyncError> {
480 let mut callback = Some(callback);
481 let sender = self.endpoint(1, true, move |value| callback.take().unwrap()(value))?;
482 Ok(Completion(sender))
483 }
484 pub fn channel<T: Send + 'static>(
485 &self,
486 capacity: usize,
487 callback: impl FnMut(T) + 'static,
488 ) -> Result<(UiSender<T>, UiSubscription), AsyncError> {
489 let sender = self.endpoint(capacity, false, callback)?;
490 let subscription = UiSubscription {
491 owner: self.0.owner.clone(),
492 id: sender.queue.id,
493 };
494 Ok((sender, subscription))
495 }
496 pub fn default_channel<T: Send + 'static>(
497 &self,
498 callback: impl FnMut(T) + 'static,
499 ) -> Result<(UiSender<T>, UiSubscription), AsyncError> {
500 self.channel(self.owner()?.limits.channel_capacity, callback)
501 }
502 fn endpoint<T: Send + 'static>(
503 &self,
504 capacity: usize,
505 once: bool,
506 mut callback: impl FnMut(T) + 'static,
507 ) -> Result<UiSender<T>, AsyncError> {
508 if capacity == 0 {
509 return Err(AsyncError::ZeroCapacity);
510 }
511 let owner = self.owner()?;
512 if owner.endpoints.borrow().len() >= owner.limits.endpoints {
513 return Err(AsyncError::Limit("endpoint"));
514 }
515 if capacity > owner.limits.message_slots.saturating_sub(owner.slots.get()) {
516 return Err(AsyncError::Limit("message slot"));
517 }
518 let id = owner.next_id();
519 let queue = Arc::new(Queue {
520 id,
521 capacity,
522 authority: self.0.authority.clone(),
523 hub: Arc::downgrade(&owner.hub),
524 state: Mutex::new(QueueState {
525 values: VecDeque::new(),
526 closed: false,
527 producers: 1,
528 waiters: HashMap::new(),
529 next_waiter: 0,
530 }),
531 });
532 owner.slots.set(owner.slots.get() + capacity);
533 owner.endpoints.borrow_mut().insert(
534 id,
535 Rc::new(Endpoint {
536 scope: self.clone(),
537 queue: queue.clone(),
538 once,
539 callback: RefCell::new(Box::new(move |payload| {
540 callback(*payload.downcast().unwrap())
541 })),
542 }),
543 );
544 Ok(UiSender {
545 queue,
546 _payload: PhantomData,
547 })
548 }
549 pub fn register_task(
552 &self,
553 start: impl FnOnce(TaskReporter) -> Box<dyn FnOnce()> + 'static,
554 ) -> Result<TaskControl, AsyncError> {
555 let owner = self.owner()?;
556 if owner.tasks.borrow().len() >= owner.limits.tasks {
557 return Err(AsyncError::Limit("task"));
558 }
559 let id = owner.next_id();
560 let status = Property::new(TaskStatus::Staged);
561 owner.tasks.borrow_mut().insert(
562 id,
563 TaskEntry {
564 scope: self.clone(),
565 status: status.clone(),
566 start: Some(Box::new(start)),
567 cancel: None,
568 },
569 );
570 if owner.active.get() {
571 owner.start_task(id);
572 }
573 Ok(TaskControl {
574 scope: self.clone(),
575 status,
576 })
577 }
578}
579
580#[derive(Clone, Debug, Default, PartialEq, Eq)]
582pub enum TaskStatus {
583 #[default]
584 Staged,
585 Running,
586 CancelRequested,
587 Completed,
588 Cancelled,
589 Panicked(String),
590}
591impl Interpolatable for TaskStatus {}
592#[derive(Debug)]
594pub enum TaskOutcome<T> {
595 Completed(T),
596 Panicked(String),
597}
598#[derive(Clone)]
600pub struct TaskControl {
601 scope: AsyncScope,
602 status: Property<TaskStatus>,
603}
604impl TaskControl {
605 pub fn cancel(&self) {
606 self.scope.cancel();
607 }
608 pub fn status(&self) -> TaskStatus {
609 self.status.get()
610 }
611 pub fn status_property(&self) -> crate::properties::Published<TaskStatus> {
614 self.status.published()
615 }
616}
617pub struct TaskReporter {
620 id: u64,
621 status: Property<TaskStatus>,
622 hub: std::sync::Weak<Hub>,
623 finished: bool,
624}
625impl TaskReporter {
626 pub fn finish(mut self, outcome: TaskStatus) {
627 if let TaskStatus::Panicked(error) = &outcome {
628 log::error!("async task panicked: {error}");
629 }
630 self.status.set(outcome);
631 self.finished = true;
632 if let Some(hub) = self.hub.upgrade() {
633 hub.notify(self.id);
634 }
635 }
636}
637impl Drop for TaskReporter {
638 fn drop(&mut self) {
639 if !self.finished {
640 self.status.set(TaskStatus::Cancelled);
641 if let Some(hub) = self.hub.upgrade() {
642 hub.notify(self.id);
643 }
644 }
645 }
646}
647
648type StartTask = Box<dyn FnOnce(TaskReporter) -> Box<dyn FnOnce()>>;
649struct TaskEntry {
650 scope: AsyncScope,
651 status: Property<TaskStatus>,
652 start: Option<StartTask>,
653 cancel: Option<Box<dyn FnOnce()>>,
654}
655struct Endpoint {
656 scope: AsyncScope,
657 queue: Arc<Queue>,
658 once: bool,
659 callback: RefCell<Box<dyn FnMut(Payload)>>,
660}
661struct DispatcherInner {
662 limits: AsyncLimits,
663 active: Cell<bool>,
664 root: Authority,
665 sequence: Cell<u64>,
666 hub: Arc<Hub>,
667 scopes: RefCell<HashMap<u64, Weak<ScopeInner>>>,
668 endpoints: RefCell<HashMap<u64, Rc<Endpoint>>>,
669 tasks: RefCell<HashMap<u64, TaskEntry>>,
670 slots: Cell<usize>,
671 publishers: Arc<AtomicUsize>,
672 error_sink: RefCell<Rc<dyn Fn(String)>>,
673 draining: Cell<bool>,
674}
675impl DispatcherInner {
676 fn next_id(&self) -> u64 {
677 let id = self
678 .sequence
679 .get()
680 .checked_add(1)
681 .expect("async identity exhausted");
682 self.sequence.set(id);
683 id
684 }
685 fn scope(self: &Rc<Self>, parent: Option<AsyncScope>) -> Result<AsyncScope, AsyncError> {
686 if !self.root.live() {
687 return Err(AsyncError::Closed);
688 }
689 if self.scopes.borrow().len() >= self.limits.scopes {
690 return Err(AsyncError::Limit("scope"));
691 }
692 let id = self.next_id();
693 let authority = parent
694 .as_ref()
695 .map_or_else(|| self.root.child(), |parent| parent.0.authority.child());
696 let callback_context = parent
697 .as_ref()
698 .and_then(|parent| parent.0.callback_context.borrow().clone());
699 let inner = Rc::new(ScopeInner {
700 id,
701 owner: Rc::downgrade(self),
702 authority,
703 callback_context: RefCell::new(callback_context),
704 _parent: parent,
705 });
706 self.scopes.borrow_mut().insert(id, Rc::downgrade(&inner));
707 Ok(AsyncScope(inner))
708 }
709 fn remove_endpoint(&self, id: u64) {
710 let endpoint = self.endpoints.borrow_mut().remove(&id);
711 if let Some(endpoint) = endpoint {
712 self.slots.set(self.slots.get() - endpoint.queue.capacity);
713 endpoint.queue.close();
714 self.hub.forget(id);
715 drop(endpoint);
716 }
717 }
718 fn start_task(&self, id: u64) {
719 let start = {
720 let mut tasks = self.tasks.borrow_mut();
721 let Some(task) = tasks.get_mut(&id) else {
722 return;
723 };
724 if task.scope.is_closed() {
725 return;
726 }
727 task.start.take().map(|start| (start, task.status.clone()))
728 };
729 if let Some((start, status)) = start {
730 status.set(TaskStatus::Running);
731 let reporter = TaskReporter {
732 id,
733 status: status.clone(),
734 hub: Arc::downgrade(&self.hub),
735 finished: false,
736 };
737 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| start(reporter))) {
738 Ok(cancel) => {
739 let mut cancel = Some(cancel);
740 if let Some(task) = self.tasks.borrow_mut().get_mut(&id) {
741 if !task.scope.is_closed() {
742 task.cancel = cancel.take();
743 }
744 }
745 if let Some(cancel) = cancel {
746 cancel();
747 }
748 }
749 Err(panic) => {
750 status.set(TaskStatus::Panicked(panic_message(panic)));
751 self.hub.notify(id);
752 }
753 }
754 }
755 }
756 fn sweep_cancelled(&self) {
757 let endpoints: Vec<_> = self
758 .endpoints
759 .borrow()
760 .iter()
761 .filter(|(_, endpoint)| endpoint.scope.is_closed())
762 .map(|(id, _)| *id)
763 .collect();
764 for id in endpoints {
765 self.remove_endpoint(id);
766 }
767 let cancelled: Vec<_> = self
768 .tasks
769 .borrow()
770 .iter()
771 .filter(|(_, task)| task.scope.is_closed())
772 .map(|(id, _)| *id)
773 .collect();
774 for id in cancelled {
775 let (staged, cancel, status) = {
776 let mut tasks = self.tasks.borrow_mut();
777 let Some(task) = tasks.get_mut(&id) else {
778 continue;
779 };
780 (task.start.take(), task.cancel.take(), task.status.clone())
781 };
782 if staged.is_some() {
783 status.set(TaskStatus::Cancelled);
784 drop(staged);
785 self.hub.notify(id);
786 } else if let Some(cancel) = cancel {
787 if matches!(status.get(), TaskStatus::Running) {
788 status.set(TaskStatus::CancelRequested);
789 }
790 cancel();
791 }
792 }
793 }
794}
795impl Drop for DispatcherInner {
796 fn drop(&mut self) {
797 self.root.revoke();
798 for endpoint in self.endpoints.get_mut().values() {
799 endpoint.queue.close();
800 }
801 for task in self.tasks.get_mut().values_mut() {
802 if let Some(cancel) = task.cancel.take() {
803 cancel();
804 }
805 }
806 }
807}
808
809#[doc(hidden)]
812#[derive(Clone)]
813pub struct AsyncDispatcher(Rc<DispatcherInner>);
814impl AsyncDispatcher {
815 pub fn new(limits: AsyncLimits) -> Self {
816 Self(Rc::new(DispatcherInner {
817 limits,
818 active: Cell::new(false),
819 root: Authority::root(),
820 sequence: Cell::new(0),
821 hub: Arc::new(Hub {
822 ready: Mutex::new(Ready::default()),
823 wake: Mutex::new(Arc::new(|| {})),
824 }),
825 scopes: RefCell::new(HashMap::new()),
826 endpoints: RefCell::new(HashMap::new()),
827 tasks: RefCell::new(HashMap::new()),
828 slots: Cell::new(0),
829 publishers: Arc::new(AtomicUsize::new(0)),
830 error_sink: RefCell::new(Rc::new(|error| log::error!("async task failed: {error}"))),
831 draining: Cell::new(false),
832 }))
833 }
834 pub fn scope(&self) -> Result<AsyncScope, AsyncError> {
835 self.0.scope(None)
836 }
837 pub fn set_waker(&self, wake: impl Fn() + Send + Sync + 'static) {
838 *lock(&self.0.hub.wake) = Arc::new(wake);
839 if self.has_pending() {
840 self.0.hub.wake();
841 }
842 }
843 pub fn set_error_sink(&self, sink: impl Fn(String) + 'static) {
844 *self.0.error_sink.borrow_mut() = Rc::new(sink);
845 }
846 pub fn has_pending(&self) -> bool {
847 self.0.active.get() && !lock(&self.0.hub.ready).order.is_empty()
848 }
849 pub fn activate(&self) {
850 if self.0.active.replace(true) || !self.0.root.live() {
851 return;
852 }
853 let tasks: Vec<_> = self.0.tasks.borrow().keys().copied().collect();
854 for id in tasks {
855 self.0.start_task(id);
856 }
857 if self.has_pending() {
858 self.0.hub.wake();
859 }
860 }
861 pub fn close(&self) {
862 self.0.root.revoke();
863 self.0.sweep_cancelled();
864 }
865 pub fn task_count(&self) -> usize {
866 self.0.tasks.borrow().len()
867 }
868 pub fn counts(&self) -> (usize, usize, usize, usize) {
869 (
870 self.0.scopes.borrow().len(),
871 self.0.tasks.borrow().len(),
872 self.0.endpoints.borrow().len(),
873 self.0.publishers.load(Ordering::Relaxed),
874 )
875 }
876 pub fn drain(&self, mut settle: impl FnMut()) -> Result<usize, String> {
877 if !self.0.active.get() || self.0.draining.replace(true) {
878 return Ok(0);
879 }
880 struct Reset<'a>(&'a Cell<bool>);
881 impl Drop for Reset<'_> {
882 fn drop(&mut self) {
883 self.0.set(false);
884 }
885 }
886 let _reset = Reset(&self.0.draining);
887 let mut ready = VecDeque::new();
888 for id in self.0.hub.take() {
889 let task = {
890 let mut tasks = self.0.tasks.borrow_mut();
891 if tasks.get(&id).is_some_and(|task| {
892 matches!(
893 task.status.get(),
894 TaskStatus::Completed | TaskStatus::Cancelled | TaskStatus::Panicked(_)
895 )
896 }) {
897 tasks.remove(&id)
898 } else {
899 None
900 }
901 };
902 if let Some(task) = task {
903 if let TaskStatus::Panicked(error) = task.status.get() {
904 let sink = self.0.error_sink.borrow().clone();
905 if let Err(panic) =
906 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink(error)))
907 {
908 self.close();
909 return Err(panic_message(panic));
910 }
911 }
912 drop(task);
913 }
914 let endpoint = self.0.endpoints.borrow().get(&id).cloned();
915 if let Some(endpoint) = endpoint {
916 let quota = lock(&endpoint.queue.state).values.len();
917 if quota == 0 && endpoint.queue.finished() {
918 self.0.remove_endpoint(id);
919 } else if quota > 0 {
920 ready.push_back((id, quota));
921 }
922 }
923 }
924 let started = monotonic_now();
925 let mut delivered = 0;
926 while !ready.is_empty()
927 && delivered < self.0.limits.callbacks_per_turn
928 && (delivered == 0
929 || monotonic_now().saturating_sub(started) < self.0.limits.callback_time)
930 {
931 settle();
932 let (id, quota) = ready.pop_front().unwrap();
933 let endpoint = self.0.endpoints.borrow().get(&id).cloned();
934 let Some(endpoint) = endpoint else {
935 continue;
936 };
937 if endpoint.scope.is_closed() {
938 self.0.remove_endpoint(id);
939 continue;
940 }
941 if let Some(value) = endpoint.queue.pop() {
942 let context = endpoint.scope.0.callback_context.borrow().clone();
943 let mut value = Some(value);
944 let mut deliver = || {
945 (endpoint.callback.borrow_mut())(
946 value.take().expect("callback context invoked twice"),
947 )
948 };
949 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
950 if let Some(context) = context {
951 context(&mut deliver);
952 } else {
953 deliver();
954 }
955 }));
956 if let Err(panic) = result {
957 self.close();
958 return Err(panic_message(panic));
959 }
960 delivered += 1;
961 if endpoint.once || endpoint.queue.finished() {
962 self.0.remove_endpoint(id);
963 } else if quota > 1 {
964 ready.push_back((id, quota - 1));
965 }
966 }
967 }
968 self.0.hub.prepend(ready.into_iter().map(|(id, _)| id));
971 if self.has_pending() {
972 self.0.hub.wake();
973 }
974 Ok(delivered)
975 }
976}
977
978pub fn panic_message(panic: Box<dyn Any + Send>) -> String {
980 panic
981 .downcast_ref::<String>()
982 .cloned()
983 .or_else(|| {
984 panic
985 .downcast_ref::<&str>()
986 .map(|value| (*value).to_owned())
987 })
988 .unwrap_or_else(|| "task panicked with a non-string payload".into())
989}
990
991pub trait AsyncScopeSource {
994 fn work_scope(&self) -> Result<AsyncScope, AsyncError>;
995}
996
997#[cfg(test)]
998mod tests {
999 use super::*;
1000 use std::{cell::RefCell, sync::atomic::AtomicBool, task::Wake};
1001 fn dispatcher() -> (AsyncDispatcher, AsyncScope) {
1002 let dispatcher = AsyncDispatcher::new(AsyncLimits::default());
1003 let scope = dispatcher.scope().unwrap();
1004 dispatcher.activate();
1005 (dispatcher, scope)
1006 }
1007 struct Flag(AtomicBool);
1008 impl Wake for Flag {
1009 fn wake(self: Arc<Self>) {
1010 self.0.store(true, Ordering::SeqCst);
1011 }
1012 }
1013 #[test]
1014 fn worker_completion_runs_and_drops_its_local_capture_only_on_the_owner() {
1015 let (dispatcher, scope) = dispatcher();
1016 let owner = std::thread::current().id();
1017 let value = Rc::new(Cell::new(0));
1018 let captured = value.clone();
1019 let completion = scope
1020 .completion(move |result| {
1021 assert_eq!(std::thread::current().id(), owner);
1022 captured.set(result);
1023 })
1024 .unwrap();
1025 std::thread::spawn(move || completion.complete(42).unwrap())
1026 .join()
1027 .unwrap();
1028 assert_eq!(value.get(), 0);
1029 dispatcher.drain(|| {}).unwrap();
1030 assert_eq!(value.get(), 42);
1031 assert_eq!(Rc::strong_count(&value), 1);
1032 assert_eq!(dispatcher.counts().2, 0);
1033 }
1034 #[test]
1035 fn unused_producer_drop_releases_callback_on_next_owner_turn() {
1036 let (dispatcher, scope) = dispatcher();
1037 let local = Rc::new(());
1038 let capture = local.clone();
1039 let completion = scope.completion(move |_: ()| drop(capture)).unwrap();
1040 std::thread::spawn(move || drop(completion)).join().unwrap();
1041 assert_eq!(Rc::strong_count(&local), 2);
1042 dispatcher.drain(|| {}).unwrap();
1043 assert_eq!(Rc::strong_count(&local), 1);
1044 }
1045 #[test]
1046 fn structural_settlement_precedes_every_callback_and_revokes_queued_work() {
1047 let (dispatcher, scope) = dispatcher();
1048 let node = scope.child().unwrap();
1049 let removed = Rc::new(Cell::new(false));
1050 let remove = removed.clone();
1051 let first = scope.completion(move |_: ()| remove.set(true)).unwrap();
1052 let second = node
1053 .completion(|_: ()| panic!("removed node callback must not start"))
1054 .unwrap();
1055 first.complete(()).unwrap();
1056 second.complete(()).unwrap();
1057 assert_eq!(
1058 dispatcher
1059 .drain(|| {
1060 if removed.get() {
1061 node.cancel();
1062 }
1063 })
1064 .unwrap(),
1065 1
1066 );
1067 assert_eq!(dispatcher.counts().2, 0);
1068 }
1069 #[test]
1070 fn callbacks_enqueue_for_a_later_drain_and_channels_rotate_under_budget() {
1071 let mut limits = AsyncLimits::default();
1072 limits.callbacks_per_turn = 2;
1073 let dispatcher = AsyncDispatcher::new(limits);
1074 let scope = dispatcher.scope().unwrap();
1075 dispatcher.activate();
1076 let history = Rc::new(RefCell::new(Vec::new()));
1077 let record = history.clone();
1078 let (a, _a) = scope
1079 .channel(4, move |value| record.borrow_mut().push(value))
1080 .unwrap();
1081 let record = history.clone();
1082 let a2 = a.clone();
1083 let (b, _b) = scope
1084 .channel(2, move |value| {
1085 record.borrow_mut().push(value);
1086 a2.try_send(9).unwrap();
1087 })
1088 .unwrap();
1089 a.try_send(1).unwrap();
1090 a.try_send(2).unwrap();
1091 b.try_send(3).unwrap();
1092 dispatcher.drain(|| {}).unwrap();
1093 assert_eq!(*history.borrow(), [1, 3]);
1094 dispatcher.drain(|| {}).unwrap();
1095 assert_eq!(*history.borrow(), [1, 3, 2, 9]);
1096 }
1097 #[test]
1098 fn closure_wakes_capacity_waiters_and_returns_their_values() {
1099 let (dispatcher, scope) = dispatcher();
1100 let (sender, subscription) = scope.channel(1, |_: usize| {}).unwrap();
1101 sender.try_send(1).unwrap();
1102 assert_eq!(sender.try_send(2), Err(TrySendError::Full(2)));
1103 let wake = Arc::new(Flag(AtomicBool::new(false)));
1104 let waker = Waker::from(wake.clone());
1105 let mut cx = Context::from_waker(&waker);
1106 let mut future = Box::pin(sender.send(3));
1107 assert!(future.as_mut().poll(&mut cx).is_pending());
1108 drop(subscription);
1109 assert!(wake.0.load(Ordering::SeqCst));
1110 assert_eq!(future.as_mut().poll(&mut cx), Poll::Ready(Err(Closed(3))));
1111 dispatcher.drain(|| {}).unwrap();
1112 assert_eq!(dispatcher.counts().2, 0);
1113 }
1114 #[test]
1115 fn late_guarded_writes_cannot_overwrite_replacement_even_from_retained_workers() {
1116 let (_dispatcher, scope) = dispatcher();
1117 let target = Property::new(0usize);
1118 let mut previous = None;
1119 let old = scope.replace(&mut previous, &target, 1).unwrap();
1120 let publisher = old.publisher(&target).unwrap();
1121 publisher.set(2).unwrap();
1122 let next = scope.replace(&mut previous, &target, 3).unwrap();
1123 assert_eq!(
1124 std::thread::spawn(move || publisher.set(99))
1125 .join()
1126 .unwrap(),
1127 Err(Closed(99))
1128 );
1129 assert_eq!(target.get(), 3);
1130 next.publisher(&target).unwrap().set(4).unwrap();
1131 scope.cancel();
1132 assert_eq!(target.get(), 4);
1133 }
1134 #[test]
1135 fn revocation_racing_a_worker_commit_cannot_overwrite_replacement_state() {
1136 let (_dispatcher, scope) = dispatcher();
1137 let target = Property::new(0usize);
1138 for _ in 0..128 {
1139 let operation = scope.child().unwrap();
1140 let publisher = operation.publisher(&target).unwrap();
1141 let start = Arc::new(std::sync::Barrier::new(2));
1142 let worker_start = start.clone();
1143 let worker = std::thread::spawn(move || {
1144 worker_start.wait();
1145 let _ = publisher.set(99);
1146 });
1147 start.wait();
1148 operation.cancel();
1149 target.set(7);
1150 worker.join().unwrap();
1151 assert_eq!(target.get(), 7);
1152 }
1153 }
1154 #[test]
1155 fn guarded_commit_releases_authority_before_waking_arbitrary_host_code() {
1156 let (_dispatcher, scope) = dispatcher();
1157 let authority = scope.0.authority.clone();
1158 let graph = crate::PropertyGraph::new(move || authority.revoke());
1159 let _entered = graph.enter();
1160 let target = Property::new(0usize);
1161 let _view = target.local();
1162 let publisher = scope.publisher(&target).unwrap();
1163 publisher.set(1).unwrap();
1164 assert!(publisher.is_closed());
1165 assert_eq!(target.get(), 1);
1166 }
1167 #[test]
1168 fn prepared_tasks_are_staged_and_discarded_without_starting() {
1169 let dispatcher = AsyncDispatcher::new(AsyncLimits::default());
1170 let scope = dispatcher.scope().unwrap();
1171 let task = scope
1172 .register_task(|_| panic!("discarded candidate executed a task"))
1173 .unwrap();
1174 assert_eq!(task.status(), TaskStatus::Staged);
1175 dispatcher.close();
1176 dispatcher.activate();
1177 assert_eq!(task.status(), TaskStatus::Cancelled);
1178 }
1179 #[test]
1180 fn dropping_controls_does_not_detach_tasks_and_panics_reach_the_sink() {
1181 let (dispatcher, scope) = dispatcher();
1182 let errors = Rc::new(RefCell::new(Vec::new()));
1183 let captured = errors.clone();
1184 dispatcher.set_error_sink(move |error| captured.borrow_mut().push(error));
1185 let reporter = Rc::new(RefCell::new(None));
1186 let captured = reporter.clone();
1187 let control = scope
1188 .child()
1189 .unwrap()
1190 .register_task(move |signal| {
1191 *captured.borrow_mut() = Some(signal);
1192 Box::new(|| {})
1193 })
1194 .unwrap();
1195 drop(control);
1196 assert_eq!(dispatcher.task_count(), 1);
1197 reporter
1198 .borrow_mut()
1199 .take()
1200 .unwrap()
1201 .finish(TaskStatus::Panicked("expected failure".into()));
1202 dispatcher.drain(|| {}).unwrap();
1203 assert_eq!(*errors.borrow(), ["expected failure"]);
1204 assert_eq!(dispatcher.task_count(), 0);
1205 assert_eq!(dispatcher.counts().0, 1);
1206 }
1207 #[test]
1208 fn resources_are_bounded_and_reusable_after_completion_or_drop() {
1209 let mut limits = AsyncLimits::default();
1210 limits.endpoints = 1;
1211 limits.message_slots = 2;
1212 limits.publishers = 1;
1213 limits.tasks = 1;
1214 let dispatcher = AsyncDispatcher::new(limits);
1215 let scope = dispatcher.scope().unwrap();
1216 dispatcher.activate();
1217 assert!(matches!(
1218 scope.channel(3, |_: ()| {}),
1219 Err(AsyncError::Limit("message slot"))
1220 ));
1221 let completion = scope.completion(|_: ()| {}).unwrap();
1222 assert!(matches!(
1223 scope.completion(|_: ()| {}),
1224 Err(AsyncError::Limit("endpoint"))
1225 ));
1226 drop(completion);
1227 dispatcher.drain(|| {}).unwrap();
1228 let completion = scope.completion(|_: ()| {}).unwrap();
1229 drop(completion);
1230 dispatcher.drain(|| {}).unwrap();
1231 let value = Property::new(0usize);
1232 let publisher = scope.publisher(&value).unwrap();
1233 assert!(matches!(
1234 scope.publisher(&value),
1235 Err(AsyncError::Limit("publisher"))
1236 ));
1237 drop(publisher);
1238 assert!(scope.publisher(&value).is_ok());
1239 }
1240 #[test]
1241 fn cancelling_parent_closes_descendants_and_waiters_before_returning() {
1242 let (_dispatcher, root) = dispatcher();
1243 let child = root.child().unwrap();
1244 let (sender, _subscription) = child.channel(1, |_: usize| {}).unwrap();
1245 sender.try_send(1).unwrap();
1246 let wake = Arc::new(Flag(AtomicBool::new(false)));
1247 let waker = Waker::from(wake.clone());
1248 let mut cx = Context::from_waker(&waker);
1249 let mut send = Box::pin(sender.send(2));
1250 assert!(send.as_mut().poll(&mut cx).is_pending());
1251 root.cancel();
1252 assert!(wake.0.load(Ordering::SeqCst));
1253 assert_eq!(send.as_mut().poll(&mut cx), Poll::Ready(Err(Closed(2))));
1254 assert!(child.is_closed());
1255 }
1256}
1257
1258pub(crate) fn monotonic_now() -> Duration {
1260 #[cfg(not(target_arch = "wasm32"))]
1261 {
1262 static ORIGIN: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
1263 ORIGIN.get_or_init(std::time::Instant::now).elapsed()
1264 }
1265 #[cfg(target_arch = "wasm32")]
1266 {
1267 use wasm_bindgen::prelude::*;
1268 #[wasm_bindgen]
1269 extern "C" {
1270 #[wasm_bindgen(js_namespace = performance, js_name = now)]
1271 fn performance_now() -> f64;
1272 }
1273 Duration::from_secs_f64(performance_now() / 1000.0)
1274 }
1275}