1use async_trait::async_trait;
109use hyperactor::Actor;
110use hyperactor::ActorEnvironment;
111use hyperactor::ActorHandle;
112use hyperactor::ActorRef;
113use hyperactor::Context;
114use hyperactor::Endpoint;
115use hyperactor::Handler;
116use hyperactor::Instance;
117use hyperactor::OncePortHandle;
118use hyperactor::RemoteSpawn;
119use hyperactor::Uid;
120use hyperactor::context;
121use hyperactor_config::Flattrs;
122
123use crate::Gspawn;
124use crate::KeepaliveLink;
125use crate::SpawnActor as SpawnActorMessage;
126use crate::SupervisionOptions;
127use crate::Supervisor;
128use crate::Worker;
129use crate::supervision::GspawnAndSupervise;
130
131hyperactor::behavior!(SpawnActor, SpawnActorMessage);
133
134#[derive(Debug, Default)]
136#[hyperactor::export(SpawnActorMessage)]
137#[hyperactor::spawnable]
138pub struct ActorSpawner;
139
140#[async_trait]
141impl Actor for ActorSpawner {
142 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
143 this.set_system();
144 Ok(())
145 }
146}
147
148#[async_trait]
149impl Handler<SpawnActorMessage> for ActorSpawner {
150 async fn handle(
151 &mut self,
152 cx: &Context<Self>,
153 message: SpawnActorMessage,
154 ) -> anyhow::Result<()> {
155 let SpawnActorMessage { gspawn, supervise } = message;
156 let worker = context::Actor::instance(cx).spawn(Worker::new());
157 worker.post(cx, GspawnAndSupervise::new(gspawn, supervise));
158 Ok(())
159 }
160}
161
162hyperactor::assert_behaves!(ActorSpawner as SpawnActor);
163
164pub trait ActorSpawnerEndpoint {
166 fn spawn<A>(&self, cx: &impl context::Actor, params: A::Params) -> anyhow::Result<ActorRef<A>>
168 where
169 A: RemoteSpawn,
170 Self: Clone + Send + 'static,
171 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
172 {
173 self.spawn_uid::<A>(cx, Uid::anonymous(), params)
174 }
175
176 fn spawn_with_handle<A>(
182 &self,
183 cx: &impl context::Actor,
184 params: A::Params,
185 ) -> anyhow::Result<(ActorRef<A>, ActorHandle<Supervisor>)>
186 where
187 A: RemoteSpawn,
188 Self: Clone + Send + 'static,
189 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
190 {
191 self.spawn_uid_with_link_and_ready::<A>(
192 cx,
193 Uid::anonymous(),
194 params,
195 KeepaliveLink::default(),
196 None,
197 cx.instance().actor_environment().clone(),
198 Flattrs::new(),
199 )
200 }
201
202 fn spawn_uid<A>(
204 &self,
205 cx: &impl context::Actor,
206 uid: Uid,
207 params: A::Params,
208 ) -> anyhow::Result<ActorRef<A>>
209 where
210 A: RemoteSpawn,
211 Self: Clone + Send + 'static,
212 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
213 {
214 self.spawn_uid_with_link::<A>(cx, uid, params, KeepaliveLink::default())
215 }
216
217 fn spawn_uid_with_ready<A>(
224 &self,
225 cx: &impl context::Actor,
226 uid: Uid,
227 params: A::Params,
228 ready: OncePortHandle<()>,
229 ) -> anyhow::Result<ActorRef<A>>
230 where
231 A: RemoteSpawn,
232 Self: Clone + Send + 'static,
233 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
234 {
235 self.spawn_uid_with_link_and_ready::<A>(
236 cx,
237 uid,
238 params,
239 KeepaliveLink::default(),
240 Some(ready),
241 cx.instance().actor_environment().clone(),
242 Flattrs::new(),
243 )
244 .map(|(actor_ref, _supervisor)| actor_ref)
245 }
246
247 fn spawn_uid_with_link<A>(
249 &self,
250 cx: &impl context::Actor,
251 uid: Uid,
252 params: A::Params,
253 liveness: KeepaliveLink,
254 ) -> anyhow::Result<ActorRef<A>>
255 where
256 A: RemoteSpawn,
257 Self: Clone + Send + 'static,
258 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
259 {
260 self.spawn_uid_with_link_and_ready::<A>(
261 cx,
262 uid,
263 params,
264 liveness,
265 None,
266 cx.instance().actor_environment().clone(),
267 Flattrs::new(),
268 )
269 .map(|(actor_ref, _supervisor)| actor_ref)
270 }
271
272 fn spawn_uid_with_link_and_ready<A>(
279 &self,
280 cx: &impl context::Actor,
281 uid: Uid,
282 params: A::Params,
283 liveness: KeepaliveLink,
284 ready: Option<OncePortHandle<()>>,
285 environment: ActorEnvironment,
286 labels: Flattrs,
287 ) -> anyhow::Result<(ActorRef<A>, ActorHandle<Supervisor>)>
288 where
289 A: RemoteSpawn,
290 Self: Clone + Send + 'static,
291 for<'a> &'a Self: Endpoint<SpawnActorMessage>,
292 {
293 anyhow::ensure!(
297 uid.is_instance(),
298 "ActorSpawner-spawned actors cannot be singletons"
299 );
300 let actor_ref = ActorRef::attest(
301 self.endpoint_location()
302 .actor_addr()
303 .proc_addr()
304 .actor_addr_uid(uid.clone()),
305 );
306 let actor_spawner = self.clone();
307 let gspawn = Gspawn::for_actor_uid_in_environment::<A>(uid, params, environment)?;
308 let supervisor = cx.instance().spawn(
309 Supervisor::bootstrap_uid(
310 liveness,
311 SupervisionOptions::default(),
312 Uid::anonymous(),
313 actor_ref.actor_addr().clone(),
314 ready,
315 move |cx, supervise| {
316 actor_spawner.post(cx, SpawnActorMessage { gspawn, supervise });
317 Ok(())
318 },
319 )
320 .with_labels(labels),
321 );
322 Ok((actor_ref, supervisor))
323 }
324}
325
326impl<T> ActorSpawnerEndpoint for T where for<'a> &'a T: Endpoint<SpawnActorMessage> {}
327
328#[cfg(test)]
329mod tests {
330 use std::time::Duration;
331
332 use async_trait::async_trait;
333 use hyperactor::Actor;
334 use hyperactor::ActorAddr;
335 use hyperactor::ActorEnvironment;
336 use hyperactor::ActorHandle;
337 use hyperactor::Context;
338 use hyperactor::Handler;
339 use hyperactor::Instance;
340 use hyperactor::Label;
341 use hyperactor::OncePortHandle;
342 use hyperactor::OncePortRef;
343 use hyperactor::PortRef;
344 use hyperactor::Proc;
345 use hyperactor::RemoteSpawn;
346 use hyperactor::Uid;
347 use hyperactor::supervision::ActorSupervisionEvent;
348 use hyperactor_config::attrs::declare_attrs;
349 use serde::Deserialize;
350 use serde::Serialize;
351 use typeuri::Named;
352
353 use super::*;
354 use crate::Gspawn;
355 use crate::KeepaliveLink;
356 use crate::RemoteActorDisposition;
357 use crate::Supervise;
358 use crate::SupervisionOptions;
359 use crate::SupervisorEvent;
360
361 #[derive(Clone, Debug, Serialize, Deserialize, Named)]
362 struct Fail;
363 wirevalue::register_type!(Fail);
364
365 #[derive(Debug)]
366 #[hyperactor::export(Fail)]
367 struct TestChild;
368
369 #[async_trait]
370 impl Actor for TestChild {}
371
372 #[async_trait]
373 impl RemoteSpawn for TestChild {
374 type Params = ();
375
376 async fn new(_params: (), _environment: &ActorEnvironment) -> anyhow::Result<Self> {
377 Ok(Self)
378 }
379 }
380
381 #[async_trait]
382 impl Handler<Fail> for TestChild {
383 async fn handle(&mut self, _cx: &Context<Self>, _message: Fail) -> anyhow::Result<()> {
384 anyhow::bail!("test child failed")
385 }
386 }
387
388 hyperactor::register_spawnable!(TestChild);
389
390 #[derive(Debug)]
391 #[hyperactor::export(handlers = [])]
392 struct FailingChild;
393
394 #[async_trait]
395 impl Actor for FailingChild {
396 async fn init(&mut self, _this: &Instance<Self>) -> anyhow::Result<()> {
397 anyhow::bail!("failing child init")
398 }
399 }
400
401 #[async_trait]
402 impl RemoteSpawn for FailingChild {
403 type Params = ();
404
405 async fn new(_params: (), _environment: &ActorEnvironment) -> anyhow::Result<Self> {
406 Ok(Self)
407 }
408 }
409
410 hyperactor::register_spawnable!(FailingChild);
411
412 declare_attrs! {
413 attr REMOTE_ENVIRONMENT_TAG: u64;
414 attr REMOTE_SERVER_ONLY_TAG: u64;
415 }
416
417 #[derive(Debug)]
418 #[hyperactor::export(handlers = [])]
419 struct EnvironmentChild {
420 reply: PortRef<(u64, u64, Option<u64>, Option<u64>)>,
421 constructor_value: u64,
422 constructor_server_only: Option<u64>,
423 }
424
425 #[async_trait]
426 impl Actor for EnvironmentChild {
427 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
428 let stored_value = this
429 .actor_environment()
430 .get(REMOTE_ENVIRONMENT_TAG)
431 .unwrap_or_default();
432 self.reply.post(
433 this,
434 (
435 self.constructor_value,
436 stored_value,
437 self.constructor_server_only,
438 this.actor_environment().get(REMOTE_SERVER_ONLY_TAG),
439 ),
440 );
441 Ok(())
442 }
443 }
444
445 #[async_trait]
446 impl RemoteSpawn for EnvironmentChild {
447 type Params = PortRef<(u64, u64, Option<u64>, Option<u64>)>;
448
449 async fn new(reply: Self::Params, environment: &ActorEnvironment) -> anyhow::Result<Self> {
450 Ok(Self {
451 reply,
452 constructor_value: environment.get(REMOTE_ENVIRONMENT_TAG).unwrap_or_default(),
453 constructor_server_only: environment.get(REMOTE_SERVER_ONLY_TAG),
454 })
455 }
456 }
457
458 hyperactor::register_spawnable!(EnvironmentChild);
459
460 #[derive(Debug)]
461 struct PlainSpawner {
462 actor_spawner: ActorHandle<ActorSpawner>,
463 spawned: PortRef<ActorAddr>,
464 events: PortRef<ActorSupervisionEvent>,
465 }
466
467 #[async_trait]
468 impl Actor for PlainSpawner {
469 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
470 let actor_ref = self.actor_spawner.spawn_uid::<FailingChild>(
471 this,
472 Uid::instance(Label::new("failing_child").unwrap()),
473 (),
474 )?;
475 self.spawned.post(this, actor_ref.actor_addr().clone());
476 Ok(())
477 }
478
479 async fn handle_supervision_event(
480 &mut self,
481 this: &Instance<Self>,
482 event: &ActorSupervisionEvent,
483 ) -> anyhow::Result<bool> {
484 self.events.post(this, event.clone());
485 this.exit("supervision event observed")?;
486 Ok(true)
487 }
488 }
489
490 #[derive(Debug)]
491 struct ReadySpawner {
492 actor_spawner: ActorHandle<ActorSpawner>,
493 spawned: PortRef<ActorAddr>,
494 ready: Option<OncePortHandle<()>>,
495 }
496
497 #[async_trait]
498 impl Actor for ReadySpawner {
499 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
500 let ready = self.ready.take().expect("ready spawner initialized once");
501 let actor_ref = self.actor_spawner.spawn_uid_with_ready::<TestChild>(
502 this,
503 Uid::instance(Label::new("ready_child").unwrap()),
504 (),
505 ready,
506 )?;
507 self.spawned.post(this, actor_ref.actor_addr().clone());
508 Ok(())
509 }
510
511 async fn handle_supervision_event(
512 &mut self,
513 _this: &Instance<Self>,
514 event: &ActorSupervisionEvent,
515 ) -> anyhow::Result<bool> {
516 Ok(!event.is_error())
518 }
519 }
520
521 #[derive(Clone, Debug, Serialize, Deserialize, Named)]
522 struct Ping(OncePortRef<()>);
523 wirevalue::register_type!(Ping);
524
525 #[derive(Debug)]
526 #[hyperactor::export(Ping)]
527 struct StoppingSpawner {
528 actor_spawner: ActorHandle<ActorSpawner>,
529 stopped: Option<OncePortHandle<()>>,
530 }
531
532 #[async_trait]
533 impl Actor for StoppingSpawner {
534 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
535 let (_child, supervisor) = self
536 .actor_spawner
537 .spawn_with_handle::<TestChild>(this, ())?;
538 supervisor.stop("test complete")?;
539 let _status = supervisor.await;
540 self.stopped
541 .take()
542 .expect("stopping spawner initialized once")
543 .post(this, ());
544 Ok(())
545 }
546 }
547
548 #[async_trait]
549 impl Handler<Ping> for StoppingSpawner {
550 async fn handle(&mut self, cx: &Context<Self>, message: Ping) -> anyhow::Result<()> {
551 message.0.post(cx, ());
552 Ok(())
553 }
554 }
555
556 #[derive(Debug)]
557 #[hyperactor::export(Ping)]
558 struct SlowChild;
559
560 #[async_trait]
561 impl Actor for SlowChild {}
562
563 #[async_trait]
564 impl RemoteSpawn for SlowChild {
565 type Params = ();
566
567 async fn new(_params: (), _environment: &ActorEnvironment) -> anyhow::Result<Self> {
568 tokio::time::sleep(Duration::from_millis(500)).await;
570 Ok(Self)
571 }
572 }
573
574 #[async_trait]
575 impl Handler<Ping> for SlowChild {
576 async fn handle(&mut self, cx: &Context<Self>, message: Ping) -> anyhow::Result<()> {
577 message.0.post(cx, ());
578 Ok(())
579 }
580 }
581
582 hyperactor::register_spawnable!(SlowChild);
583
584 #[derive(Debug)]
585 struct EagerSpawner {
586 actor_spawner: ActorHandle<ActorSpawner>,
587 pong: Option<OncePortRef<()>>,
588 }
589
590 #[async_trait]
591 impl Actor for EagerSpawner {
592 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
593 let child = self.actor_spawner.spawn_uid::<SlowChild>(
594 this,
595 Uid::instance(Label::new("slow_child").unwrap()),
596 (),
597 )?;
598 let pong = self.pong.take().expect("eager spawner initialized once");
599 child.post(this, Ping(pong));
600 Ok(())
601 }
602
603 async fn handle_supervision_event(
604 &mut self,
605 _this: &Instance<Self>,
606 event: &ActorSupervisionEvent,
607 ) -> anyhow::Result<bool> {
608 Ok(!event.is_error())
610 }
611 }
612
613 fn encoded_unit() -> Vec<u8> {
614 bincode::serde::encode_to_vec((), bincode::config::legacy()).unwrap()
615 }
616
617 async fn spawn_test_child(
618 actor_spawner: &ActorHandle<ActorSpawner>,
619 client: &hyperactor::Client,
620 supervisor: PortRef<SupervisorEvent>,
621 session_id: Uid,
622 ) {
623 let (_link_handle, liveness) =
624 KeepaliveLink::new(Duration::from_secs(60), Duration::from_secs(60))
625 .spawn_supervisor(client)
626 .unwrap();
627 actor_spawner.post(
628 client,
629 SpawnActorMessage {
630 gspawn: Gspawn::for_actor_uid::<TestChild>(
631 client,
632 Uid::instance(Label::new("test_child").unwrap()),
633 (),
634 )
635 .unwrap(),
636 supervise: Supervise {
637 session_id,
638 supervisor,
639 parent: client.self_addr().clone(),
640 liveness,
641 options: SupervisionOptions::default(),
642 labels: Flattrs::new(),
643 },
644 },
645 );
646 }
647
648 #[tokio::test]
649 async fn test_spawn_actor_endpoint_works_from_plain_actor() {
650 let proc = Proc::isolated();
651 let client = proc.client("client");
652 let actor_spawner = proc.spawn(ActorSpawner);
653 let (spawned, mut spawned_rx) = client.open_port::<ActorAddr>();
654 let (events, mut events_rx) = client.open_port::<ActorSupervisionEvent>();
655 let spawner = proc.spawn(PlainSpawner {
656 actor_spawner: actor_spawner.clone(),
657 spawned: spawned.bind(),
658 events: events.bind(),
659 });
660
661 let spawned_actor = tokio::time::timeout(Duration::from_secs(5), spawned_rx.recv())
662 .await
663 .unwrap()
664 .unwrap();
665 let event = tokio::time::timeout(Duration::from_secs(5), events_rx.recv())
666 .await
667 .unwrap()
668 .unwrap();
669 assert!(event.is_error());
670 assert_eq!(event.actor_id, spawned_actor);
671
672 actor_spawner.stop("test").unwrap();
673 tokio::time::timeout(Duration::from_secs(5), spawner)
674 .await
675 .unwrap();
676 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
677 .await
678 .unwrap();
679 }
680
681 #[tokio::test]
682 async fn test_spawn_uid_with_ready_signals_when_child_links() {
683 let proc = Proc::isolated();
684 let client = proc.client("client");
685 let actor_spawner = proc.spawn(ActorSpawner);
686 let (spawned, mut spawned_rx) = client.open_port::<ActorAddr>();
687 let (ready, ready_rx) = client.open_once_port::<()>();
688 let spawner = proc.spawn(ReadySpawner {
689 actor_spawner: actor_spawner.clone(),
690 spawned: spawned.bind(),
691 ready: Some(ready),
692 });
693
694 let _attested = tokio::time::timeout(Duration::from_secs(5), spawned_rx.recv())
695 .await
696 .unwrap()
697 .unwrap();
698 tokio::time::timeout(Duration::from_secs(5), ready_rx.recv())
702 .await
703 .unwrap()
704 .unwrap();
705
706 spawner.stop("test").unwrap();
707 tokio::time::timeout(Duration::from_secs(5), spawner)
708 .await
709 .unwrap();
710 actor_spawner.stop("test").unwrap();
711 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
712 .await
713 .unwrap();
714 }
715
716 #[tokio::test]
717 async fn test_spawn_uid_delivers_messages_sent_before_child_starts() {
718 let proc = Proc::isolated();
719 let client = proc.client("client");
720 let actor_spawner = proc.spawn(ActorSpawner);
721 let (pong, pong_rx) = client.open_once_port::<()>();
722 let spawner = proc.spawn(EagerSpawner {
723 actor_spawner: actor_spawner.clone(),
724 pong: Some(pong.bind()),
725 });
726
727 tokio::time::timeout(Duration::from_secs(5), pong_rx.recv())
728 .await
729 .unwrap()
730 .unwrap();
731
732 spawner.stop("test").unwrap();
733 tokio::time::timeout(Duration::from_secs(5), spawner)
734 .await
735 .unwrap();
736 actor_spawner.stop("test").unwrap();
737 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
738 .await
739 .unwrap();
740 }
741
742 #[tokio::test]
743 async fn test_spawn_with_handle_stops_only_spawned_actor() {
744 let proc = Proc::isolated();
745 let client = proc.client("client");
746 let actor_spawner = proc.spawn(ActorSpawner);
747 let (stopped, stopped_rx) = client.open_once_port::<()>();
748 let parent = proc.spawn(StoppingSpawner {
749 actor_spawner: actor_spawner.clone(),
750 stopped: Some(stopped),
751 });
752
753 tokio::time::timeout(Duration::from_secs(5), stopped_rx.recv())
754 .await
755 .expect("spawned actor stop timed out")
756 .expect("spawned actor stop signal closed");
757 assert!(
758 !parent.status().borrow().is_terminal(),
759 "stopping the spawned actor should not stop its parent"
760 );
761 assert!(
762 !actor_spawner.status().borrow().is_terminal(),
763 "stopping the spawned actor should not stop the actor spawner"
764 );
765
766 let (pong, pong_rx) = client.open_once_port::<()>();
767 parent.post(&client, Ping(pong.bind()));
768 tokio::time::timeout(Duration::from_secs(5), pong_rx.recv())
769 .await
770 .expect("parent ping timed out")
771 .expect("parent ping port closed");
772
773 parent.stop("test complete").expect("stop parent");
774 tokio::time::timeout(Duration::from_secs(5), parent)
775 .await
776 .expect("parent stop timed out");
777 actor_spawner.stop("test").expect("stop actor spawner");
778 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
779 .await
780 .expect("actor spawner stop timed out");
781 }
782
783 #[tokio::test]
784 async fn actor_spawner_carries_callers_actor_environment() {
785 const SENTINEL: u64 = 0xA0FE;
786
787 let proc = Proc::isolated();
788 let client = proc.client("client");
789 let mut server_environment = ActorEnvironment::default();
790 server_environment
791 .set(REMOTE_ENVIRONMENT_TAG, SENTINEL + 1)
792 .expect("seed conflicting server environment");
793 server_environment
794 .set(REMOTE_SERVER_ONLY_TAG, 99)
795 .expect("seed server-only environment");
796 let server = proc
797 .actor_instance_in_environment::<TestChild>("seeded_server", server_environment)
798 .expect("create seeded server instance");
799 let actor_spawner = server.instance.spawn(ActorSpawner);
800 let (reply, mut reply_rx) = client.open_port::<(u64, u64, Option<u64>, Option<u64>)>();
801 let (ready, ready_rx) = client.open_once_port::<()>();
802
803 let mut environment = ActorEnvironment::default();
804 environment
805 .set(REMOTE_ENVIRONMENT_TAG, SENTINEL)
806 .expect("seed caller environment");
807 let caller = proc
808 .actor_instance_in_environment::<TestChild>("seeded_caller", environment)
809 .expect("create seeded caller instance");
810
811 actor_spawner
812 .spawn_uid_with_ready::<EnvironmentChild>(
813 &caller.instance,
814 Uid::instance(Label::new("environment_child").unwrap()),
815 reply.bind(),
816 ready,
817 )
818 .expect("request remote child");
819 tokio::time::timeout(Duration::from_secs(5), ready_rx.recv())
820 .await
821 .expect("remote child readiness timed out")
822 .expect("remote child readiness port closed");
823 assert_eq!(
824 tokio::time::timeout(Duration::from_secs(5), reply_rx.recv())
825 .await
826 .expect("environment reply timed out")
827 .expect("environment reply port closed"),
828 (SENTINEL, SENTINEL, None, None),
829 "constructor and stored instance must receive only the caller's environment",
830 );
831
832 actor_spawner.stop("test").expect("stop actor spawner");
833 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
834 .await
835 .expect("actor spawner stop timed out");
836 }
837
838 #[test]
839 fn gspawn_serializes_actor_environment() {
840 let mut environment = ActorEnvironment::default();
841 environment
842 .set(REMOTE_ENVIRONMENT_TAG, 17)
843 .expect("seed actor environment");
844 let gspawn = Gspawn::with_uid(
845 TestChild::typename(),
846 Uid::instance(Label::new("serialized_child").unwrap()),
847 encoded_unit(),
848 environment,
849 );
850
851 let encoded = bincode::serde::encode_to_vec(&gspawn, bincode::config::legacy())
852 .expect("serialize gspawn");
853 let restored: Gspawn =
854 bincode::serde::decode_from_slice(&encoded, bincode::config::legacy())
855 .map(|(value, _)| value)
856 .expect("deserialize gspawn");
857
858 assert_eq!(restored, gspawn);
859 assert_eq!(
860 restored.actor_environment().get(REMOTE_ENVIRONMENT_TAG),
861 Some(17),
862 );
863 }
864
865 #[tokio::test]
866 async fn test_actor_spawner_worker_reports_child_failure() {
867 let proc = Proc::isolated();
868 let client = proc.client("client");
869 let actor_spawner = proc.spawn(ActorSpawner);
870 let (supervisor, mut supervisor_rx) = client.open_port::<SupervisorEvent>();
871 let session_id = Uid::instance(Label::new("session").unwrap());
872
873 spawn_test_child(
874 &actor_spawner,
875 &client,
876 supervisor.bind(),
877 session_id.clone(),
878 )
879 .await;
880 let linked = tokio::time::timeout(Duration::from_secs(5), supervisor_rx.recv())
881 .await
882 .unwrap()
883 .unwrap();
884 let (session_id, child) = match linked {
885 SupervisorEvent::Linked {
886 session_id, child, ..
887 } => (session_id, child),
888 message => panic!("expected linked message, got {:?}", message),
889 };
890 PortRef::<Fail>::attest_handler_port(&child).post(&client, Fail);
891
892 let event = tokio::time::timeout(Duration::from_secs(5), supervisor_rx.recv())
893 .await
894 .unwrap()
895 .unwrap();
896 match event {
897 SupervisorEvent::SupervisionEvent {
898 session_id: event_session_id,
899 event: ActorSupervisionEvent { actor_id, .. },
900 disposition: RemoteActorDisposition::Terminal,
901 } if event_session_id == session_id && actor_id == child => {}
902 message => panic!("expected terminal supervision event, got {:?}", message),
903 }
904
905 actor_spawner.stop("test").unwrap();
906 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
907 .await
908 .unwrap();
909 }
910
911 #[tokio::test]
912 async fn test_actor_spawner_rejects_unknown_actor_type() {
913 let proc = Proc::isolated();
914 let client = proc.client("client");
915 let actor_spawner = proc.spawn(ActorSpawner);
916 let (supervisor, mut supervisor_rx) = client.open_port::<SupervisorEvent>();
917 let (_link_handle, liveness) =
918 KeepaliveLink::new(Duration::from_secs(60), Duration::from_secs(60))
919 .spawn_supervisor(&client)
920 .unwrap();
921 let session_id = Uid::instance(Label::new("session").unwrap());
922
923 actor_spawner.post(
924 &client,
925 SpawnActorMessage {
926 gspawn: Gspawn::with_uid(
927 "unknown::Actor",
928 Uid::anonymous(),
929 encoded_unit(),
930 ActorEnvironment::default(),
931 ),
932 supervise: Supervise {
933 session_id: session_id.clone(),
934 supervisor: supervisor.bind(),
935 parent: client.self_addr().clone(),
936 liveness,
937 options: SupervisionOptions::default(),
938 labels: Flattrs::new(),
939 },
940 },
941 );
942
943 let rejected = tokio::time::timeout(Duration::from_secs(5), supervisor_rx.recv())
944 .await
945 .unwrap()
946 .unwrap();
947 match rejected {
948 SupervisorEvent::SuperviseRejected {
949 session_id: rejected_session_id,
950 reason,
951 } if rejected_session_id == session_id
952 && reason.contains("actor type unknown::Actor not registered") => {}
953 message => panic!("expected supervise rejection, got {:?}", message),
954 }
955
956 actor_spawner.stop("test").unwrap();
957 tokio::time::timeout(Duration::from_secs(5), actor_spawner)
958 .await
959 .unwrap();
960 }
961}