hyperactor_remote/
link.rs1use hyperactor::Actor;
17use hyperactor::AnyActorHandle;
18use hyperactor::Data;
19#[cfg(test)]
20use hyperactor::Instance;
21use hyperactor::Uid;
22use hyperactor::context;
23use serde::Deserialize;
24use serde::Serialize;
25use typeuri::Named;
26
27#[derive(Clone, Debug, Serialize, Deserialize, Named, PartialEq, Eq)]
33pub struct LinkSpec {
34 actor_type: String,
35 uid: Uid,
36 params: Data,
37}
38wirevalue::register_type!(LinkSpec);
39
40impl LinkSpec {
41 pub fn for_actor<A: Actor + Named>(params: Data) -> Self {
43 Self::new(A::typename(), params)
44 }
45
46 pub fn for_actor_uid<A: Actor + Named>(uid: Uid, params: Data) -> Self {
48 Self::with_uid(A::typename(), uid, params)
49 }
50
51 pub fn new(actor_type: impl Into<String>, params: Data) -> Self {
53 Self::with_uid(actor_type, Uid::anonymous(), params)
54 }
55
56 pub fn with_uid(actor_type: impl Into<String>, uid: Uid, params: Data) -> Self {
58 Self {
59 actor_type: actor_type.into(),
60 uid,
61 params,
62 }
63 }
64
65 pub fn actor_type(&self) -> &str {
67 &self.actor_type
68 }
69
70 pub fn uid(&self) -> &Uid {
72 &self.uid
73 }
74
75 pub fn params(&self) -> &[u8] {
77 &self.params
78 }
79
80 pub async fn spawn_worker<C: context::Actor>(
82 self,
83 parent: &C,
84 ) -> anyhow::Result<AnyActorHandle> {
85 parent
86 .instance()
87 .gspawn_uid(&self.actor_type, self.uid, self.params)
88 .await
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use async_trait::async_trait;
95 use hyperactor::Actor;
96 use hyperactor::ActorEnvironment;
97 use hyperactor::Context;
98 use hyperactor::Endpoint as _;
99 use hyperactor::Handler;
100 use hyperactor::Label;
101 use hyperactor::PortRef;
102 use hyperactor::Proc;
103 use hyperactor::RemoteSpawn;
104 use hyperactor::Uid;
105 use hyperactor::actor::ActorErrorKind;
106 use hyperactor::actor::ActorStatus;
107 use hyperactor::supervision::ActorSupervisionEvent;
108
109 use super::*;
110
111 #[derive(Debug)]
112 #[hyperactor::export(())]
113 struct TestLinkActor;
114
115 #[async_trait]
116 impl Actor for TestLinkActor {}
117
118 #[async_trait]
119 impl RemoteSpawn for TestLinkActor {
120 type Params = ();
121
122 async fn new(_params: (), _environment: &ActorEnvironment) -> anyhow::Result<Self> {
123 Ok(Self)
124 }
125 }
126
127 #[async_trait]
128 impl Handler<()> for TestLinkActor {
129 async fn handle(&mut self, _cx: &Context<Self>, _message: ()) -> anyhow::Result<()> {
130 Ok(())
131 }
132 }
133
134 hyperactor::register_spawnable!(TestLinkActor);
135
136 #[derive(Debug)]
137 struct TestParentActor {
138 link: Option<LinkSpec>,
139 events: PortRef<ActorSupervisionEvent>,
140 }
141
142 #[async_trait]
143 impl Actor for TestParentActor {
144 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
145 let link = self.link.take().unwrap();
146 let handle = link.spawn_worker(this).await?;
147 handle.kill("link failed")?;
148 Ok(())
149 }
150
151 async fn handle_supervision_event(
152 &mut self,
153 this: &Instance<Self>,
154 event: &ActorSupervisionEvent,
155 ) -> anyhow::Result<bool> {
156 self.events.post(this, event.clone());
157 Ok(true)
158 }
159 }
160
161 #[tokio::test]
162 async fn test_link_spec_spawns_supervised_child() {
163 let proc = Proc::isolated();
164 let parent = proc.client("parent");
165 let uid = Uid::instance(Label::new("link").unwrap());
166
167 let link = LinkSpec::for_actor_uid::<TestLinkActor>(
168 uid.clone(),
169 bincode::serde::encode_to_vec((), bincode::config::legacy()).unwrap(),
170 );
171
172 let handle = link.spawn_worker(&parent).await.unwrap();
173
174 assert_eq!(handle.actor_id().uid(), &uid);
175 assert!(handle.downcast::<TestLinkActor>().is_some());
176
177 handle.stop("test").unwrap();
178 handle.await;
179 }
180
181 #[tokio::test]
182 async fn test_link_actor_failure_propagates_to_parent() {
183 let proc = Proc::isolated();
184 let client = proc.client("client");
185 let (events, mut event_rx) = client.open_port::<ActorSupervisionEvent>();
186 let uid = Uid::instance(Label::new("link").unwrap());
187 let link = LinkSpec::for_actor_uid::<TestLinkActor>(
188 uid.clone(),
189 bincode::serde::encode_to_vec((), bincode::config::legacy()).unwrap(),
190 );
191 let parent = proc.spawn(TestParentActor {
192 link: Some(link),
193 events: events.bind(),
194 });
195
196 let event = event_rx.recv().await.unwrap();
197
198 assert_eq!(event.actor_id.uid(), &uid);
199 assert!(matches!(
200 event.actor_status,
201 ActorStatus::Failed(ActorErrorKind::Generic(ref reason))
202 if reason == "actor explicitly aborted due to: link failed"
203 ));
204 assert!(!event.actor_id.is_root());
205
206 parent.stop("test").unwrap();
207 parent.await;
208 }
209}