1#[cfg(test)]
15use std::collections::HashMap;
16use std::collections::VecDeque;
17use std::ops::Deref;
18#[cfg(test)]
19use std::time::Duration;
20
21use async_trait::async_trait;
22use hyperactor::Actor;
23use hyperactor::ActorRef;
24use hyperactor::Context;
25use hyperactor::Endpoint as _;
26use hyperactor::Handler;
27use hyperactor::Instance;
28use hyperactor::RefClient;
29#[cfg(test)]
30use hyperactor::context;
31use hyperactor::ordering::SEQ_INFO;
32use hyperactor::ordering::SeqInfo;
33use hyperactor::supervision::ActorSupervisionEvent;
34use hyperactor_config::Flattrs;
35use hyperactor_config::global::Source;
36use ndslice::Point;
37#[cfg(test)]
38use ndslice::ViewExt as _;
39use serde::Deserialize;
40use serde::Serialize;
41use typeuri::Named;
42#[cfg(test)]
43use uuid::Uuid;
44
45use crate::ActorMesh;
46#[cfg(test)]
47use crate::ActorMeshRef;
48use crate::ProcMeshRef;
49use crate::comm::multicast::CastInfo;
50use crate::mesh_id::ActorMeshId;
51use crate::supervision::MeshFailure;
52#[cfg(test)]
53use crate::testing;
54
55#[derive(Default, Debug)]
57#[hyperactor::export(
58 (),
59 GetActorId,
60 GetCastInfo,
61 GetResourceRank,
62 CauseSupervisionEvent,
63 Forward,
64 GetConfigAttrs,
65 SetConfigAttrs,
66)]
67#[hyperactor::spawnable]
68pub struct TestActor;
69
70impl Actor for TestActor {}
71
72#[derive(Debug, Clone, Named, Serialize, Deserialize)]
74pub struct GetActorId(pub hyperactor::PortRef<(hyperactor::ActorAddr, Option<SeqInfo>)>);
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub enum SupervisionEventType {
78 Panic,
79 SigSEGV,
80 ProcessExit(i32),
81}
82
83#[derive(Debug, Clone, Named, Serialize, Deserialize)]
86pub struct CauseSupervisionEvent {
87 pub kind: SupervisionEventType,
88 pub send_to_children: bool,
89}
90
91impl CauseSupervisionEvent {
92 fn cause_event(&self) -> ! {
93 match self.kind {
94 SupervisionEventType::Panic => {
95 panic!("for testing");
96 }
97 SupervisionEventType::SigSEGV => {
98 tracing::error!("exiting with SIGSEGV");
99 unsafe { std::ptr::null_mut::<i32>().write(42) };
101 panic!("should have segfaulted");
104 }
105 SupervisionEventType::ProcessExit(code) => {
106 tracing::error!("exiting process {} with code {}", std::process::id(), code);
107 std::process::exit(code);
108 }
109 }
110 }
111}
112
113#[async_trait]
114impl Handler<()> for TestActor {
115 async fn handle(&mut self, _cx: &Context<Self>, _: ()) -> Result<(), anyhow::Error> {
116 Ok(())
117 }
118}
119
120#[async_trait]
121impl Handler<GetActorId> for TestActor {
122 async fn handle(
123 &mut self,
124 cx: &Context<Self>,
125 GetActorId(reply): GetActorId,
126 ) -> Result<(), anyhow::Error> {
127 let seq_info = cx.headers().get(SEQ_INFO);
128 reply.post(cx, (cx.self_addr().clone(), seq_info));
129 Ok(())
130 }
131}
132
133#[async_trait]
134impl Handler<CauseSupervisionEvent> for TestActor {
135 async fn handle(
136 &mut self,
137 _cx: &Context<Self>,
138 msg: CauseSupervisionEvent,
139 ) -> Result<(), anyhow::Error> {
140 msg.cause_event();
141 }
142}
143
144#[derive(Default, Debug)]
147#[hyperactor::export(ActorSupervisionEvent)]
148#[hyperactor::spawnable]
149pub struct TestActorWithSupervisionHandling;
150
151#[async_trait]
152impl Actor for TestActorWithSupervisionHandling {
153 async fn handle_supervision_event(
154 &mut self,
155 _this: &Instance<Self>,
156 event: &ActorSupervisionEvent,
157 ) -> Result<bool, anyhow::Error> {
158 tracing::error!("supervision event: {:?}", event);
159 Ok(true)
161 }
162}
163
164#[async_trait]
165impl Handler<ActorSupervisionEvent> for TestActorWithSupervisionHandling {
166 async fn handle(
167 &mut self,
168 _cx: &Context<Self>,
169 _msg: ActorSupervisionEvent,
170 ) -> Result<(), anyhow::Error> {
171 Ok(())
172 }
173}
174
175#[derive(Default, Debug)]
178#[hyperactor::export(std::time::Duration)]
179#[hyperactor::spawnable]
180pub struct SleepActor;
181
182impl Actor for SleepActor {}
183
184#[async_trait]
185impl Handler<std::time::Duration> for SleepActor {
186 async fn handle(
187 &mut self,
188 _cx: &Context<Self>,
189 duration: std::time::Duration,
190 ) -> Result<(), anyhow::Error> {
191 tokio::time::sleep(duration).await;
192 Ok(())
193 }
194}
195
196#[derive(Debug, Clone, Named, Serialize, Deserialize)]
200pub struct Forward {
201 pub to_visit: VecDeque<hyperactor::PortRef<Forward>>,
202 pub visited: Vec<hyperactor::PortRef<Forward>>,
203}
204
205#[async_trait]
206impl Handler<Forward> for TestActor {
207 async fn handle(
208 &mut self,
209 cx: &Context<Self>,
210 Forward {
211 mut to_visit,
212 mut visited,
213 }: Forward,
214 ) -> Result<(), anyhow::Error> {
215 let Some(this) = to_visit.pop_front() else {
216 anyhow::bail!("unexpected forward chain termination");
217 };
218 visited.push(this);
219 let next = to_visit.front().cloned();
220 anyhow::ensure!(next.is_some(), "unexpected forward chain termination");
221 next.unwrap().post(cx, Forward { to_visit, visited });
222 Ok(())
223 }
224}
225
226#[derive(Debug, Clone, Named, Serialize, Deserialize, Handler, RefClient)]
228pub struct GetCastInfo {
229 #[reply]
231 pub cast_info: hyperactor::PortRef<(Point, ActorRef<TestActor>, hyperactor::ActorAddr)>,
232}
233
234#[async_trait]
235impl Handler<GetCastInfo> for TestActor {
236 async fn handle(
237 &mut self,
238 cx: &Context<Self>,
239 GetCastInfo { cast_info }: GetCastInfo,
240 ) -> Result<(), anyhow::Error> {
241 cast_info.post(cx, (cx.cast_point(), cx.bind(), cx.sender().clone()));
242 Ok(())
243 }
244}
245
246#[derive(Debug, Clone, Named, Serialize, Deserialize)]
247pub struct GetResourceRank {
248 pub rank: crate::resource::Rank,
249 pub reply: hyperactor::PortRef<(Point, Option<usize>)>,
250}
251
252#[async_trait]
253impl Handler<GetResourceRank> for TestActor {
254 async fn handle(
255 &mut self,
256 cx: &Context<Self>,
257 GetResourceRank { rank, reply }: GetResourceRank,
258 ) -> Result<(), anyhow::Error> {
259 reply.post(cx, (cx.cast_point(), rank.0));
260
261 Ok(())
262 }
263}
264
265#[derive(Debug)]
266#[hyperactor::export]
267#[hyperactor::spawnable]
268pub struct FailingCreateTestActor;
269
270#[async_trait]
271impl Actor for FailingCreateTestActor {}
272
273#[async_trait]
274impl hyperactor::RemoteSpawn for FailingCreateTestActor {
275 type Params = ();
276
277 async fn new(
278 _params: Self::Params,
279 _environment: Flattrs,
280 ) -> Result<Self, hyperactor::internal_macro_support::anyhow::Error> {
281 Err(anyhow::anyhow!("test failure"))
282 }
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize, Named)]
286pub struct SetConfigAttrs(pub Vec<u8>);
287
288#[async_trait]
289impl Handler<SetConfigAttrs> for TestActor {
290 async fn handle(
291 &mut self,
292 _cx: &Context<Self>,
293 SetConfigAttrs(attrs): SetConfigAttrs,
294 ) -> Result<(), anyhow::Error> {
295 let attrs =
296 bincode::serde::decode_from_slice(&attrs, bincode::config::legacy()).map(|(v, _)| v)?;
297 hyperactor_config::global::set(Source::Runtime, attrs);
298 Ok(())
299 }
300}
301
302#[derive(Clone, Debug, Serialize, Deserialize, Named)]
303pub struct GetConfigAttrs(pub hyperactor::PortRef<Vec<u8>>);
304
305#[async_trait]
306impl Handler<GetConfigAttrs> for TestActor {
307 async fn handle(
308 &mut self,
309 cx: &Context<Self>,
310 GetConfigAttrs(reply): GetConfigAttrs,
311 ) -> Result<(), anyhow::Error> {
312 let attrs = bincode::serde::encode_to_vec(
313 hyperactor_config::global::attrs(),
314 bincode::config::legacy(),
315 )?;
316 reply.post(cx, attrs);
317 Ok(())
318 }
319}
320
321#[derive(Clone, Debug, Serialize, Deserialize, Named)]
325pub struct NextSupervisionFailure(pub hyperactor::PortRef<Option<MeshFailure>>);
326
327#[derive(Debug)]
331#[hyperactor::export(CauseSupervisionEvent, MeshFailure, NextSupervisionFailure)]
332#[hyperactor::spawnable]
333pub struct WrapperActor {
334 proc_mesh: ProcMeshRef,
335 mesh: Option<ActorMesh<TestActor>>,
337 supervisor: hyperactor::PortRef<MeshFailure>,
338 test_name: ActorMeshId,
339}
340
341#[async_trait]
342impl hyperactor::RemoteSpawn for WrapperActor {
343 type Params = (ProcMeshRef, hyperactor::PortRef<MeshFailure>, ActorMeshId);
344
345 async fn new(
346 (proc_mesh, supervisor, test_name): Self::Params,
347 _environment: Flattrs,
348 ) -> Result<Self, hyperactor::internal_macro_support::anyhow::Error> {
349 Ok(Self {
350 proc_mesh,
351 mesh: None,
352 supervisor,
353 test_name,
354 })
355 }
356}
357
358#[async_trait]
359impl Actor for WrapperActor {
360 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
361 self.mesh = Some(
362 self.proc_mesh
363 .spawn_with_name(this, self.test_name.clone(), &(), None, false)
364 .await?,
365 );
366 Ok(())
367 }
368}
369
370#[async_trait]
371impl Handler<CauseSupervisionEvent> for WrapperActor {
372 async fn handle(
373 &mut self,
374 cx: &Context<Self>,
375 msg: CauseSupervisionEvent,
376 ) -> Result<(), anyhow::Error> {
377 if msg.send_to_children {
379 self.mesh
381 .as_ref()
382 .unwrap()
383 .cast(cx, msg)
384 .map_err(|e| e.into())
385 } else {
386 msg.cause_event()
387 }
388 }
389}
390
391#[async_trait]
392impl Handler<NextSupervisionFailure> for WrapperActor {
393 async fn handle(
394 &mut self,
395 cx: &Context<Self>,
396 msg: NextSupervisionFailure,
397 ) -> Result<(), anyhow::Error> {
398 let mesh = if let Some(mesh) = self.mesh.as_ref() {
399 mesh.deref()
400 } else {
401 msg.0.post(cx, None);
402 return Ok(());
403 };
404 let failure = match tokio::time::timeout(
405 tokio::time::Duration::from_secs(20),
406 mesh.next_supervision_event(cx),
407 )
408 .await
409 {
410 Ok(Ok(failure)) => Some(failure),
411 Ok(Err(_)) => None,
413 Err(_) => None,
415 };
416 msg.0.post(cx, failure);
417 Ok(())
418 }
419}
420
421#[async_trait]
422impl Handler<MeshFailure> for WrapperActor {
423 async fn handle(&mut self, cx: &Context<Self>, msg: MeshFailure) -> Result<(), anyhow::Error> {
424 tracing::info!("got supervision event from child: {}", msg);
427 let _ = self.supervisor.post(cx, msg.clone());
430 Ok(())
431 }
432}
433
434#[cfg(test)]
435pub async fn assert_mesh_shape(actor_mesh: ActorMesh<TestActor>) {
439 let instance = testing::instance();
440 assert_casting_correctness(&actor_mesh, instance, None).await;
442
443 let label = actor_mesh.extent().labels()[0].clone();
446 let size = actor_mesh.extent().sizes()[0] / 2;
447
448 let sliced_actor_mesh = actor_mesh.range(&label, 0..size).unwrap();
450 assert_casting_correctness(&sliced_actor_mesh, instance, None).await;
451}
452
453#[cfg(test)]
454pub async fn assert_casting_correctness(
457 actor_mesh: &ActorMeshRef<TestActor>,
458 instance: &impl context::Actor,
459 expected_seqs: Option<(Uuid, Vec<u64>)>,
460) {
461 let (port, mut rx) = instance.mailbox().open_port();
462 actor_mesh.cast(instance, GetActorId(port.bind())).unwrap();
463 let expected_actor_ids = actor_mesh
464 .values()
465 .map(|actor_ref| actor_ref.actor_addr().clone())
466 .collect::<Vec<_>>();
467 let mut expected: HashMap<&hyperactor::ActorAddr, Option<SeqInfo>> = match expected_seqs {
468 None => expected_actor_ids
469 .iter()
470 .map(|actor_id| (actor_id, None))
471 .collect(),
472 Some((session_id, seqs)) => expected_actor_ids
473 .iter()
474 .zip(
475 seqs.into_iter()
476 .map(|seq| Some(SeqInfo::Session { session_id, seq })),
477 )
478 .collect(),
479 };
480
481 while !expected.is_empty() {
482 let (actor_id, rcved) = rx.recv().await.unwrap();
483 let rcv_seq_info = rcved.unwrap();
484 let removed = expected.remove(&actor_id);
485 assert!(
486 removed.is_some(),
487 "got {actor_id}, expect {expected_actor_ids:?}"
488 );
489 if let Some(expected) = removed.unwrap() {
490 assert_eq!(expected, rcv_seq_info, "got different seq for {actor_id}");
491 }
492 }
493
494 tokio::time::sleep(Duration::from_secs(1)).await;
496 let result = rx.try_recv();
497 assert!(result.as_ref().unwrap().is_none(), "got {result:?}");
498}