1use crate::actor::addr::{Addr, registered};
2use crate::actor::cancel::Cancel;
3use crate::actor::event_bus::{EventBus, GlobalEventBus};
4use crate::actor::event_bus::subscribe::Event;
5use crate::actor::traits::Handler;
6use crate::actor::{AllowedSignal, ManagedActor, invoke_on_ui, short_type_name};
7use crate::trace::{self, Cause, Point};
8use futures_core::Stream;
9use std::marker::PhantomData;
10use std::time::Instant;
11
12pub struct Cx<A: 'static, M = ()> {
20 pub(super) addr: Addr<A>,
21 handling: PhantomData<fn() -> M>,
22}
23
24impl<A: 'static, M> Clone for Cx<A, M> {
25 fn clone(&self) -> Self {
26 Self::new(self.addr.clone())
27 }
28}
29
30#[derive(Clone, Copy)]
36struct Task {
37 actor: &'static str,
38 actor_id: u64,
39 output: &'static str,
40 started: Instant,
41}
42
43impl Task {
44 fn new<A: 'static>(actor_id: usize, output: &'static str) -> Self {
45 Self {
46 actor: short_type_name::<A>(),
47 actor_id: actor_id as u64,
48 output,
49 started: Instant::now(),
50 }
51 }
52
53 fn spawn(&self) -> Point {
54 Point::Spawn {
55 actor: self.actor,
56 actor_id: self.actor_id,
57 output: self.output,
58 }
59 }
60
61 fn settled(&self) -> Point {
62 Point::Settled {
63 actor: self.actor,
64 actor_id: self.actor_id,
65 output: self.output,
66 took_us: self.took_us(),
67 }
68 }
69
70 fn cancelled(&self) -> Point {
71 Point::Cancelled {
72 actor: self.actor,
73 actor_id: self.actor_id,
74 output: self.output,
75 took_us: self.took_us(),
76 }
77 }
78
79 fn took_us(&self) -> u64 {
80 self.started.elapsed().as_micros() as u64
81 }
82
83 fn ended(self, spawned: Cause, point: Point) {
87 let _resumed = trace::resume(Some(spawned));
88
89 crate::observability::mark_anywhere(move || point);
90 }
91}
92
93#[derive(Clone, Copy)]
96struct Feed {
97 actor: &'static str,
98 actor_id: u64,
99 output: &'static str,
100 started: Instant,
101}
102
103impl Feed {
104 fn new<A: 'static>(actor_id: usize, output: &'static str) -> Self {
105 Self {
106 actor: short_type_name::<A>(),
107 actor_id: actor_id as u64,
108 output,
109 started: Instant::now(),
110 }
111 }
112
113 fn opened(&self) -> Point {
114 Point::Source {
115 actor: self.actor,
116 actor_id: self.actor_id,
117 output: self.output,
118 }
119 }
120
121 fn arrived(&self, source: Cause) -> Point {
122 Point::Arrived {
123 actor: self.actor,
124 actor_id: self.actor_id,
125 output: self.output,
126 source: source.get(),
127 }
128 }
129
130 fn pulled(&self, source: Cause) -> Point {
131 Point::Pull {
132 actor: self.actor,
133 actor_id: self.actor_id,
134 output: self.output,
135 source: source.get(),
136 }
137 }
138
139 fn pull(self, opened: Cause) -> Pulling {
142 let id = trace::reserve();
143 crate::observability::begin_anywhere(id, None, move || self.pulled(opened));
144
145 Pulling {
146 id,
147 started: Instant::now(),
148 }
149 }
150
151 fn closed(self, opened: Cause, gone: bool) {
152 let point = Point::Closed {
153 actor: self.actor,
154 actor_id: self.actor_id,
155 output: self.output,
156 took_us: self.started.elapsed().as_micros() as u64,
157 gone,
158 };
159 let _resumed = trace::resume(Some(opened));
160
161 crate::observability::mark_anywhere(move || point);
162 }
163}
164
165struct Pulling {
168 id: Cause,
169 started: Instant,
170}
171
172impl Drop for Pulling {
173 fn drop(&mut self) {
174 crate::observability::end_anywhere(self.id, self.started.elapsed());
175 }
176}
177
178impl<A: 'static, M> Cx<A, M> {
179 pub(crate) fn new(addr: Addr<A>) -> Self {
180 Self {
181 addr,
182 handling: PhantomData,
183 }
184 }
185
186 pub(crate) fn handling<N>(self) -> Cx<A, N> {
188 Cx::new(self.addr)
189 }
190
191 pub fn addr(&self) -> Addr<A> {
192 self.addr.clone()
193 }
194
195 pub fn detach(&self) -> Cx<A, ()> {
197 Cx::new(self.addr.clone())
198 }
199
200 pub fn send<Out>(&self, msg: Out)
202 where
203 Out: 'static,
204 A: Handler<Out> + ManagedActor,
205 A::Flow: crate::actor::flow::Allows<M, Out>,
206 {
207 self.addr.send(msg);
208 }
209
210 pub fn publish<E>(&self, msg: E)
211 where
212 A: ManagedActor,
213 E: Event,
214 A::Signals: AllowedSignal<E>,
215 {
216 GlobalEventBus::instance().publish(msg);
217 }
218
219 pub fn publish_local<E>(&self, bus: &EventBus, msg: E)
220 where
221 A: ManagedActor,
222 E: Event,
223 A::Signals: AllowedSignal<E>,
224 {
225 bus.publish(msg);
226 }
227
228 pub fn cancellation(&self) -> Cancel {
233 self.addr.cancellation()
234 }
235
236 pub fn spawn_bg<Out, Fut>(&self, fut: Fut)
242 where
243 Out: Send + 'static,
244 A: Handler<Out> + ManagedActor,
245 A::Flow: crate::actor::flow::Allows<M, Out>,
246 Fut: Future<Output = Out> + 'static + Send,
247 {
248 self.bg(fut, false);
249 }
250
251 fn bg<Out, Fut>(&self, fut: Fut, listens: bool)
256 where
257 Out: Send + 'static,
258 A: Handler<Out> + ManagedActor,
259 A::Flow: crate::actor::flow::Allows<M, Out>,
260 Fut: Future<Output = Out> + 'static + Send,
261 {
262 let id = self.addr.id;
263 let cancel = self.addr.cancellation();
264 let task = Task::new::<A>(id, short_type_name::<Out>());
265 let spawned = trace::mark(|| task.spawn());
266
267 #[cfg(feature = "test-utils")]
268 let counted = crate::actor::event_bus::Counted::new();
269
270 crate::executor::spawn(async move {
271 let running = trace::within(Some(spawned), fut);
272 let result = match listens {
273 true => Some(running.await),
274 false => cancel.guard(running).await,
275 };
276
277 let Some(result) = result.filter(|_| !cancel.is_cancelled()) else {
278 task.ended(spawned, task.cancelled());
279 return;
280 };
281
282 let return_task = move || {
283 #[cfg(feature = "test-utils")]
284 let _counted = counted;
285
286 let settled = trace::mark_under(Some(spawned), || task.settled());
287
288 if let Some(addr) = registered::<A>(id) {
289 addr.send_under(result, Some(settled));
290 }
291 };
292
293 invoke_on_ui(return_task);
294 });
295 }
296
297 pub fn spawn_bg_with<Out, Fut, F>(&self, work: F)
310 where
311 Out: Send + 'static,
312 A: Handler<Out> + ManagedActor,
313 A::Flow: crate::actor::flow::Allows<M, Out>,
314 Fut: Future<Output = Out> + 'static + Send,
315 F: FnOnce(Cancel) -> Fut,
316 {
317 self.bg(work(self.addr.cancellation()), true);
318 }
319
320 pub fn spawn_bg_detached<Fut>(&self, fut: Fut)
323 where
324 Fut: Future<Output = ()> + 'static + Send,
325 {
326 self.bg_detached(fut, false);
327 }
328
329 pub fn spawn_bg_detached_with<Fut, F>(&self, work: F)
331 where
332 Fut: Future<Output = ()> + 'static + Send,
333 F: FnOnce(Cancel) -> Fut,
334 {
335 self.bg_detached(work(self.addr.cancellation()), true);
336 }
337
338 fn bg_detached<Fut>(&self, fut: Fut, listens: bool)
339 where
340 Fut: Future<Output = ()> + 'static + Send,
341 {
342 let cancel = self.addr.cancellation();
343 let task = Task::new::<A>(self.addr.id, "()");
344 let spawned = trace::mark(|| task.spawn());
345
346 #[cfg(feature = "test-utils")]
347 let counted = crate::actor::event_bus::Counted::new();
348
349 crate::executor::spawn(async move {
350 #[cfg(feature = "test-utils")]
351 let _counted = counted;
352
353 let running = trace::within(Some(spawned), fut);
354 let ran = if listens {
355 running.await;
356 true
357 } else {
358 cancel.guard(running).await.is_some()
359 };
360
361 let ended = match ran && !cancel.is_cancelled() {
362 true => task.settled(),
363 false => task.cancelled(),
364 };
365
366 task.ended(spawned, ended);
367 });
368 }
369
370 pub fn spawn_source<S, Out, F>(&self, source: S, into: F)
388 where
389 S: Stream + Send + 'static,
390 Out: Send + 'static,
391 F: FnMut(S::Item) -> Out + Send + 'static,
392 A: Handler<Out> + ManagedActor,
393 A::Flow: crate::actor::flow::Allows<M, Out>,
394 {
395 let id = self.addr.id;
396 let cancel = self.addr.cancellation();
397 let feed = Feed::new::<A>(id, short_type_name::<Out>());
398 let opened = trace::mark(|| feed.opened());
399
400 crate::executor::spawn(async move {
401 let pouring = trace::within(None, pour::<A, _, _, _>(id, source, into, feed, opened));
402 let ran_dry = cancel.guard(pouring).await.is_some();
403
404 feed.closed(opened, !ran_dry || cancel.is_cancelled());
405 });
406 }
407}
408
409async fn pour<A, S, Out, F>(id: usize, source: S, mut into: F, feed: Feed, opened: Cause)
413where
414 A: Handler<Out> + 'static,
415 S: Stream,
416 Out: Send + 'static,
417 F: FnMut(S::Item) -> Out,
418{
419 let mut source = std::pin::pin!(source);
420
421 loop {
422 let next = async {
423 let pulling = feed.pull(opened);
424
425 std::future::poll_fn(|cx| {
426 let _pulling = trace::resume(Some(pulling.id));
427 source.as_mut().poll_next(cx)
428 })
429 .await
430 };
431 let Some(item) = next.await else {
432 break;
433 };
434 let message = into(item);
435
436 invoke_on_ui(move || {
437 let _root = trace::resume(None);
438 let _arrived = trace::enter(|| feed.arrived(opened));
439
440 if let Some(addr) = registered::<A>(id) {
441 addr.send(message);
442 }
443 });
444
445 crate::executor::yield_now().await;
446 }
447}
448
449pub struct AsyncContext<A: 'static> {
450 actor_id: usize,
451 cancel: Cancel,
452 _phantom: PhantomData<A>,
453}
454
455impl<A: 'static> Clone for AsyncContext<A> {
456 fn clone(&self) -> Self {
457 Self {
458 actor_id: self.actor_id,
459 cancel: self.cancel.clone(),
460 _phantom: PhantomData,
461 }
462 }
463}
464
465unsafe impl<A: 'static> Send for AsyncContext<A> {}
466unsafe impl<A: 'static> Sync for AsyncContext<A> {}
467
468impl<A: 'static> AsyncContext<A> {
469 pub(crate) fn new(actor_id: usize, cancel: Cancel) -> Self {
470 Self {
471 actor_id,
472 cancel,
473 _phantom: PhantomData,
474 }
475 }
476
477 pub fn cancellation(&self) -> Cancel {
479 self.cancel.clone()
480 }
481
482 pub fn is_cancelled(&self) -> bool {
485 self.cancel.is_cancelled()
486 }
487
488 pub async fn cancelled(&self) {
491 self.cancel.cancelled().await;
492 }
493
494 pub async fn until_gone<F: Future>(&self, fut: F) -> Option<F::Output> {
496 self.cancel.guard(fut).await
497 }
498
499 pub fn publish<M>(&self, msg: M)
503 where
504 A: ManagedActor,
505 M: Event,
506 A::Signals: AllowedSignal<M>,
507 {
508 GlobalEventBus::publish(msg);
509 }
510
511 pub fn publish_local<M>(&self, bus: &EventBus, msg: M)
512 where
513 A: ManagedActor,
514 M: Event,
515 A::Signals: AllowedSignal<M>,
516 {
517 bus.publish(msg);
518 }
519
520 pub fn send<M>(&self, msg: M)
521 where
522 M: Send + 'static,
523 A: Handler<M>,
524 {
525 let id = self.actor_id;
526 let cause = trace::current();
527
528 invoke_on_ui(move || {
529 if let Some(addr) = registered::<A>(id) {
530 addr.send_under(msg, cause);
531 }
532 });
533 }
534}
535
536impl<A: 'static, M> Cx<A, M> {
537 pub fn async_ctx(&self) -> AsyncContext<A> {
538 AsyncContext::new(self.addr.id, self.addr.cancellation())
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use crate::actor::UiThreadToken;
546 use std::cell::RefCell;
547 use std::rc::Rc;
548 use std::sync::Arc;
549 use std::sync::atomic::{AtomicBool, Ordering};
550
551 struct First;
552 struct Second;
553
554 struct Chain {
555 log: Rc<RefCell<Vec<&'static str>>>,
556 }
557
558 guinea_macros::actor! {
559 Chain {
560 handlers {
561 First => { send Second, bg Second }
562 Second
563 }
564 }
565 }
566
567 impl Handler<First> for Chain {
568 fn handle(&mut self, _: First, cx: Cx<Self, First>) {
569 self.log.borrow_mut().push("first");
570 cx.send(Second);
571 }
572 }
573
574 impl Handler<Second> for Chain {
575 fn handle(&mut self, _: Second, _cx: Cx<Self, Second>) {
576 self.log.borrow_mut().push("second");
577 }
578 }
579
580 #[test]
581 fn send_from_a_handler_is_drained_by_the_same_queue() {
582 let log = Rc::new(RefCell::new(Vec::new()));
583 let addr = Addr::new(
584 Chain { log: log.clone() },
585 UiThreadToken::dangerously_create_token_unchecked(),
586 );
587
588 addr.send(First);
589
590 assert_eq!(&*log.borrow(), &["first", "second"]);
591 }
592
593 #[test]
594 fn a_watcher_hears_which_actor_handled_a_message() {
595 use crate::observability::changes::{self, Change};
596
597 let seen = Rc::new(RefCell::new(Vec::new()));
598 let sink = seen.clone();
599 changes::watch(move |change| {
600 if let Change::ActorHandled { id } = change {
601 sink.borrow_mut().push(*id);
602 }
603 });
604
605 let addr = Addr::new(
606 Chain {
607 log: Rc::new(RefCell::new(Vec::new())),
608 },
609 UiThreadToken::dangerously_create_token_unchecked(),
610 );
611 addr.send(First);
612 changes::stop_watching();
613
614 let id = addr.id();
615 assert_eq!(*seen.borrow(), [id, id], "First, then the Second it sent");
616 }
617
618 #[test]
619 fn a_chain_of_sends_is_traced_back_to_the_action_that_started_it() {
620 use crate::trace::{Cause, Record, Trace};
621
622 let seen = Rc::new(RefCell::new(Vec::<Record>::new()));
623 let sink = seen.clone();
624 trace::observe(move |trace| {
625 if let Trace::Begin(record) | Trace::Mark(record) = trace {
626 sink.borrow_mut().push(record.clone());
627 }
628 });
629
630 let addr = Addr::new(
631 Chain {
632 log: Rc::new(RefCell::new(Vec::new())),
633 },
634 UiThreadToken::dangerously_create_token_unchecked(),
635 );
636 {
637 let _action = trace::enter(|| Point::Action { message: "First" });
638 addr.send(First);
639 }
640 trace::stop_observing();
641
642 let seen = seen.borrow();
643 let find = |wanted: &dyn Fn(&Point) -> bool| -> &Record {
644 seen.iter().find(|record| wanted(&record.point)).expect("recorded")
645 };
646 let parent = |record: &Record| -> Option<Cause> { record.parent };
647
648 let action = find(&|p| matches!(p, Point::Action { .. }));
649 let send_first = find(&|p| matches!(p, Point::Send { message, .. } if message.ends_with("First")));
650 let handle_first =
651 find(&|p| matches!(p, Point::Handle { message, .. } if message.ends_with("First")));
652 let send_second =
653 find(&|p| matches!(p, Point::Send { message, .. } if message.ends_with("Second")));
654 let handle_second =
655 find(&|p| matches!(p, Point::Handle { message, .. } if message.ends_with("Second")));
656
657 assert_eq!(parent(send_first), Some(action.id));
658 assert_eq!(parent(handle_first), Some(send_first.id));
659 assert_eq!(parent(send_second), Some(handle_first.id));
660 assert_eq!(parent(handle_second), Some(send_second.id));
661 }
662
663 #[test]
664 fn disposing_an_actor_cancels_what_it_spawned() {
665 let addr = Addr::new(
666 Chain {
667 log: Rc::new(RefCell::new(Vec::new())),
668 },
669 UiThreadToken::dangerously_create_token_unchecked(),
670 );
671
672 let cx = Cx::<_, First>::new(addr.clone());
673 let cancel = cx.cancellation();
674 assert!(!cancel.is_cancelled());
675
676 addr.dispose();
677
678 assert!(cancel.is_cancelled(), "teardown has to reach the tasks too");
679 }
680
681 #[tokio::test]
682 async fn a_task_cancelled_with_its_actor_says_so_and_answers_nobody() {
683 let seen = Rc::new(RefCell::new(Vec::new()));
684 let sink = seen.clone();
685 trace::observe(move |trace| {
686 if let crate::trace::Trace::Mark(record) = trace {
687 sink.borrow_mut().push(record.point.kind());
688 }
689 });
690
691 let log = Rc::new(RefCell::new(Vec::new()));
692 let addr = Addr::new_managed(
693 Chain { log: log.clone() },
694 UiThreadToken::dangerously_create_token_unchecked(),
695 );
696
697 let cx = Cx::<_, First>::new(addr.clone());
698 cx.spawn_bg::<Second, _>(async {
699 std::future::pending::<()>().await;
700 Second
701 });
702
703 addr.dispose();
704 for _ in 0..8 {
705 tokio::task::yield_now().await;
706 }
707 trace::stop_observing();
708
709 let seen = seen.borrow();
710 assert_eq!(*seen, ["spawn", "cancelled"], "the task's whole life");
711 assert!(log.borrow().is_empty(), "nothing came back to be handled");
712 }
713
714 #[tokio::test]
715 async fn work_that_listens_for_the_token_is_left_to_wind_itself_down() {
716 let addr = Addr::new_managed(
717 Chain {
718 log: Rc::new(RefCell::new(Vec::new())),
719 },
720 UiThreadToken::dangerously_create_token_unchecked(),
721 );
722
723 let wound_down = Arc::new(AtomicBool::new(false));
724 let noted = wound_down.clone();
725
726 let cx = Cx::<_, First>::new(addr.clone());
727 cx.spawn_bg_with::<Second, _, _>(|gone| async move {
728 gone.cancelled().await;
729 tokio::task::yield_now().await;
730 noted.store(true, Ordering::SeqCst);
731 Second
732 });
733
734 addr.dispose();
735 for _ in 0..8 {
736 tokio::task::yield_now().await;
737 }
738
739 assert!(
740 wound_down.load(Ordering::SeqCst),
741 "the await after the cancellation still ran"
742 );
743 }
744
745 #[test]
746 fn detach_keeps_the_address() {
747 let log = Rc::new(RefCell::new(Vec::new()));
748 let addr = Addr::new(
749 Chain { log },
750 UiThreadToken::dangerously_create_token_unchecked(),
751 );
752
753 let cx = Cx::<_, First>::new(addr.clone());
754 let bare = cx.detach();
755
756 assert_eq!(bare.addr().id(), addr.id());
757 }
758}