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::ActorEnvironment;
24use hyperactor::ActorRef;
25use hyperactor::Context;
26use hyperactor::Endpoint as _;
27use hyperactor::Handler;
28use hyperactor::Instance;
29use hyperactor::RefClient;
30#[cfg(test)]
31use hyperactor::context;
32use hyperactor::ordering::SEQ_INFO;
33use hyperactor::ordering::SeqInfo;
34use hyperactor::supervision::ActorSupervisionEvent;
35use hyperactor_config::attrs::declare_attrs;
36use hyperactor_config::global::Source;
37use ndslice::Point;
38#[cfg(test)]
39use ndslice::ViewExt as _;
40use serde::Deserialize;
41use serde::Serialize;
42use typeuri::Named;
43#[cfg(test)]
44use uuid::Uuid;
45
46use crate::ActorMesh;
47#[cfg(test)]
48use crate::ActorMeshRef;
49use crate::ProcMeshRef;
50use crate::casting::CAST_POINT;
51use crate::casting::CastInfo;
52use crate::mesh_id::ActorMeshId;
53use crate::supervision::MeshFailure;
54#[cfg(test)]
55use crate::testing;
56
57#[derive(Default, Debug)]
59#[hyperactor::export(
60 (),
61 GetActorId,
62 GetCastInfo,
63 GetResourceRank,
64 CauseSupervisionEvent,
65 Forward,
66 GetConfigAttrs,
67 SetConfigAttrs,
68)]
69#[hyperactor::spawnable]
70pub struct TestActor;
71
72impl Actor for TestActor {}
73
74#[derive(Debug, Clone, Named, Serialize, Deserialize)]
76pub struct GetActorId(pub hyperactor::PortRef<(hyperactor::ActorAddr, Option<SeqInfo>)>);
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum SupervisionEventType {
80 Panic,
81 SigSEGV,
82 ProcessExit(i32),
83}
84
85#[derive(Debug, Clone, Named, Serialize, Deserialize)]
88pub struct CauseSupervisionEvent {
89 pub kind: SupervisionEventType,
90 pub send_to_children: bool,
91}
92
93impl CauseSupervisionEvent {
94 fn cause_event(&self) -> ! {
95 match self.kind {
96 SupervisionEventType::Panic => {
97 panic!("for testing");
98 }
99 SupervisionEventType::SigSEGV => {
100 tracing::error!("exiting with SIGSEGV");
101 unsafe { std::ptr::null_mut::<i32>().write(42) };
103 panic!("should have segfaulted");
106 }
107 SupervisionEventType::ProcessExit(code) => {
108 tracing::error!("exiting process {} with code {}", std::process::id(), code);
109 std::process::exit(code);
110 }
111 }
112 }
113}
114
115#[async_trait]
116impl Handler<()> for TestActor {
117 async fn handle(&mut self, _cx: &Context<Self>, _: ()) -> Result<(), anyhow::Error> {
118 Ok(())
119 }
120}
121
122#[async_trait]
123impl Handler<GetActorId> for TestActor {
124 async fn handle(
125 &mut self,
126 cx: &Context<Self>,
127 GetActorId(reply): GetActorId,
128 ) -> Result<(), anyhow::Error> {
129 let seq_info = cx.headers().get(SEQ_INFO);
130 reply.post(cx, (cx.self_addr().clone(), seq_info));
131 Ok(())
132 }
133}
134
135#[async_trait]
136impl Handler<CauseSupervisionEvent> for TestActor {
137 async fn handle(
138 &mut self,
139 _cx: &Context<Self>,
140 msg: CauseSupervisionEvent,
141 ) -> Result<(), anyhow::Error> {
142 msg.cause_event();
143 }
144}
145
146#[derive(Default, Debug)]
149#[hyperactor::export(ActorSupervisionEvent)]
150#[hyperactor::spawnable]
151pub struct TestActorWithSupervisionHandling;
152
153#[async_trait]
154impl Actor for TestActorWithSupervisionHandling {
155 async fn handle_supervision_event(
156 &mut self,
157 _this: &Instance<Self>,
158 event: &ActorSupervisionEvent,
159 ) -> Result<bool, anyhow::Error> {
160 tracing::error!("supervision event: {:?}", event);
161 Ok(true)
163 }
164}
165
166#[async_trait]
167impl Handler<ActorSupervisionEvent> for TestActorWithSupervisionHandling {
168 async fn handle(
169 &mut self,
170 _cx: &Context<Self>,
171 _msg: ActorSupervisionEvent,
172 ) -> Result<(), anyhow::Error> {
173 Ok(())
174 }
175}
176
177#[derive(Default, Debug)]
180#[hyperactor::export(std::time::Duration)]
181#[hyperactor::spawnable]
182pub struct SleepActor;
183
184impl Actor for SleepActor {}
185
186#[async_trait]
187impl Handler<std::time::Duration> for SleepActor {
188 async fn handle(
189 &mut self,
190 _cx: &Context<Self>,
191 duration: std::time::Duration,
192 ) -> Result<(), anyhow::Error> {
193 tokio::time::sleep(duration).await;
194 Ok(())
195 }
196}
197
198#[derive(Debug, Clone, Named, Serialize, Deserialize)]
202pub struct Forward {
203 pub to_visit: VecDeque<hyperactor::PortRef<Forward>>,
204 pub visited: Vec<hyperactor::PortRef<Forward>>,
205}
206
207#[async_trait]
208impl Handler<Forward> for TestActor {
209 async fn handle(
210 &mut self,
211 cx: &Context<Self>,
212 Forward {
213 mut to_visit,
214 mut visited,
215 }: Forward,
216 ) -> Result<(), anyhow::Error> {
217 let Some(this) = to_visit.pop_front() else {
218 anyhow::bail!("unexpected forward chain termination");
219 };
220 visited.push(this);
221 let next = to_visit.front().cloned();
222 anyhow::ensure!(next.is_some(), "unexpected forward chain termination");
223 next.unwrap().post(cx, Forward { to_visit, visited });
224 Ok(())
225 }
226}
227
228#[derive(Debug, Clone, Named, Serialize, Deserialize, Handler, RefClient)]
230pub struct GetCastInfo {
231 #[reply]
233 pub cast_info: hyperactor::PortRef<(Point, ActorRef<TestActor>, hyperactor::ActorAddr)>,
234}
235
236#[async_trait]
237impl Handler<GetCastInfo> for TestActor {
238 async fn handle(
239 &mut self,
240 cx: &Context<Self>,
241 GetCastInfo { cast_info }: GetCastInfo,
242 ) -> Result<(), anyhow::Error> {
243 cast_info.post(cx, (cx.cast_point(), cx.bind(), cx.sender().clone()));
244 Ok(())
245 }
246}
247
248#[derive(Debug, Clone, Named, Serialize, Deserialize)]
249pub struct GetResourceRank {
250 pub rank: crate::resource::Rank,
251 pub reply: hyperactor::PortRef<(Point, Option<usize>)>,
252}
253
254#[async_trait]
255impl Handler<GetResourceRank> for TestActor {
256 async fn handle(
257 &mut self,
258 cx: &Context<Self>,
259 GetResourceRank { rank, reply }: GetResourceRank,
260 ) -> Result<(), anyhow::Error> {
261 reply.post(cx, (cx.cast_point(), rank.0));
262
263 Ok(())
264 }
265}
266
267#[derive(Debug)]
268#[hyperactor::export]
269#[hyperactor::spawnable]
270pub struct FailingCreateTestActor;
271
272#[async_trait]
273impl Actor for FailingCreateTestActor {}
274
275#[async_trait]
276impl hyperactor::RemoteSpawn for FailingCreateTestActor {
277 type Params = ();
278
279 async fn new(
280 _params: Self::Params,
281 _environment: &ActorEnvironment,
282 ) -> Result<Self, hyperactor::internal_macro_support::anyhow::Error> {
283 Err(anyhow::anyhow!("test failure"))
284 }
285}
286
287declare_attrs! {
288 pub attr ACTOR_ENVIRONMENT_TEST_TAG: u64;
290}
291
292#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Named)]
295pub struct ActorEnvironmentObservation {
296 pub label: String,
297 pub persistent_tag: Option<u64>,
298 pub constructor_tag: Option<u64>,
299 pub constructor_point: Option<Point>,
300 pub stored_point: Option<Point>,
301 pub proc_addr: String,
302}
303wirevalue::register_type!(ActorEnvironmentObservation);
304
305#[derive(Clone, Debug, Serialize, Deserialize, Named)]
307pub struct ActorEnvironmentProbeParams {
308 pub label: String,
309 pub reply: hyperactor::PortRef<ActorEnvironmentObservation>,
310 pub nested: Option<(ProcMeshRef, String, String)>,
311}
312wirevalue::register_type!(ActorEnvironmentProbeParams);
313
314#[derive(Debug)]
316#[hyperactor::export(handlers = [])]
317pub struct ActorEnvironmentProbe {
318 label: String,
319 reply: hyperactor::PortRef<ActorEnvironmentObservation>,
320 nested: Option<(ProcMeshRef, String, String)>,
321 constructor_tag: Option<u64>,
322 constructor_point: Option<Point>,
323}
324
325#[async_trait]
326impl Actor for ActorEnvironmentProbe {
327 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
328 self.reply.post(
329 this,
330 ActorEnvironmentObservation {
331 label: self.label.clone(),
332 persistent_tag: this.actor_environment().get(ACTOR_ENVIRONMENT_TEST_TAG),
333 constructor_tag: self.constructor_tag,
334 constructor_point: self.constructor_point.clone(),
335 stored_point: this.actor_environment().get(CAST_POINT),
336 proc_addr: this.proc().proc_addr().to_string(),
337 },
338 );
339
340 if let Some((proc_mesh, name, label)) = self.nested.take() {
341 proc_mesh
342 .spawn_controllerless_service::<Self, _>(
343 this,
344 &name,
345 &ActorEnvironmentProbeParams {
346 label,
347 reply: self.reply.clone(),
348 nested: None,
349 },
350 )
351 .await?;
352 }
353 Ok(())
354 }
355}
356
357#[async_trait]
358impl hyperactor::RemoteSpawn for ActorEnvironmentProbe {
359 type Params = ActorEnvironmentProbeParams;
360
361 async fn new(params: Self::Params, environment: &ActorEnvironment) -> anyhow::Result<Self> {
362 Ok(Self {
363 label: params.label,
364 reply: params.reply,
365 nested: params.nested,
366 constructor_tag: environment.get(ACTOR_ENVIRONMENT_TEST_TAG),
367 constructor_point: environment.get(CAST_POINT),
368 })
369 }
370}
371
372hyperactor::register_spawnable!(ActorEnvironmentProbe);
373
374#[derive(Clone, Debug, Serialize, Deserialize, Named)]
375pub struct SetConfigAttrs(pub Vec<u8>);
376
377#[async_trait]
378impl Handler<SetConfigAttrs> for TestActor {
379 async fn handle(
380 &mut self,
381 _cx: &Context<Self>,
382 SetConfigAttrs(attrs): SetConfigAttrs,
383 ) -> Result<(), anyhow::Error> {
384 let attrs =
385 bincode::serde::decode_from_slice(&attrs, bincode::config::legacy()).map(|(v, _)| v)?;
386 hyperactor_config::global::set(Source::Runtime, attrs);
387 Ok(())
388 }
389}
390
391#[derive(Clone, Debug, Serialize, Deserialize, Named)]
392pub struct GetConfigAttrs(pub hyperactor::PortRef<Vec<u8>>);
393
394#[async_trait]
395impl Handler<GetConfigAttrs> for TestActor {
396 async fn handle(
397 &mut self,
398 cx: &Context<Self>,
399 GetConfigAttrs(reply): GetConfigAttrs,
400 ) -> Result<(), anyhow::Error> {
401 let attrs = bincode::serde::encode_to_vec(
402 hyperactor_config::global::attrs(),
403 bincode::config::legacy(),
404 )?;
405 reply.post(cx, attrs);
406 Ok(())
407 }
408}
409
410#[derive(Clone, Debug, Serialize, Deserialize, Named)]
414pub struct NextSupervisionFailure(pub hyperactor::PortRef<Option<MeshFailure>>);
415
416#[derive(Debug)]
420#[hyperactor::export(CauseSupervisionEvent, MeshFailure, NextSupervisionFailure)]
421#[hyperactor::spawnable]
422pub struct WrapperActor {
423 proc_mesh: ProcMeshRef,
424 mesh: Option<ActorMesh<TestActor>>,
426 supervisor: hyperactor::PortRef<MeshFailure>,
427 test_name: ActorMeshId,
428}
429
430#[async_trait]
431impl hyperactor::RemoteSpawn for WrapperActor {
432 type Params = (ProcMeshRef, hyperactor::PortRef<MeshFailure>, ActorMeshId);
433
434 async fn new(
435 (proc_mesh, supervisor, test_name): Self::Params,
436 _environment: &ActorEnvironment,
437 ) -> Result<Self, hyperactor::internal_macro_support::anyhow::Error> {
438 Ok(Self {
439 proc_mesh,
440 mesh: None,
441 supervisor,
442 test_name,
443 })
444 }
445}
446
447#[async_trait]
448impl Actor for WrapperActor {
449 async fn init(&mut self, this: &Instance<Self>) -> anyhow::Result<()> {
450 self.mesh = Some(
451 self.proc_mesh
452 .spawn_with_name(this, self.test_name.clone(), &(), None, false)
453 .await?,
454 );
455 Ok(())
456 }
457}
458
459#[async_trait]
460impl Handler<CauseSupervisionEvent> for WrapperActor {
461 async fn handle(
462 &mut self,
463 cx: &Context<Self>,
464 msg: CauseSupervisionEvent,
465 ) -> Result<(), anyhow::Error> {
466 if msg.send_to_children {
468 self.mesh
470 .as_ref()
471 .unwrap()
472 .cast(cx, msg)
473 .map_err(|e| e.into())
474 } else {
475 msg.cause_event()
476 }
477 }
478}
479
480#[async_trait]
481impl Handler<NextSupervisionFailure> for WrapperActor {
482 async fn handle(
483 &mut self,
484 cx: &Context<Self>,
485 msg: NextSupervisionFailure,
486 ) -> Result<(), anyhow::Error> {
487 let mesh = if let Some(mesh) = self.mesh.as_ref() {
488 mesh.deref()
489 } else {
490 msg.0.post(cx, None);
491 return Ok(());
492 };
493 let failure = match tokio::time::timeout(
494 tokio::time::Duration::from_secs(20),
495 mesh.next_supervision_event(cx),
496 )
497 .await
498 {
499 Ok(Ok(failure)) => Some(failure),
500 Ok(Err(_)) => None,
502 Err(_) => None,
504 };
505 msg.0.post(cx, failure);
506 Ok(())
507 }
508}
509
510#[async_trait]
511impl Handler<MeshFailure> for WrapperActor {
512 async fn handle(&mut self, cx: &Context<Self>, msg: MeshFailure) -> Result<(), anyhow::Error> {
513 tracing::info!("got supervision event from child: {}", msg);
516 let _ = self.supervisor.post(cx, msg.clone());
519 Ok(())
520 }
521}
522
523#[cfg(test)]
524pub async fn assert_mesh_shape(actor_mesh: ActorMesh<TestActor>) {
528 let instance = testing::instance();
529 assert_casting_correctness(&actor_mesh, instance, None).await;
531
532 let label = actor_mesh.extent().labels()[0].clone();
535 let size = actor_mesh.extent().sizes()[0] / 2;
536
537 let sliced_actor_mesh = actor_mesh.range(&label, 0..size).unwrap();
539 assert_casting_correctness(&sliced_actor_mesh, instance, None).await;
540}
541
542#[cfg(test)]
543pub async fn assert_casting_correctness(
546 actor_mesh: &ActorMeshRef<TestActor>,
547 instance: &impl context::Actor,
548 expected_seqs: Option<(Uuid, Vec<u64>)>,
549) {
550 let (port, mut rx) = instance.mailbox().open_port();
551 actor_mesh.cast(instance, GetActorId(port.bind())).unwrap();
552 let expected_actor_ids = actor_mesh
553 .values()
554 .map(|actor_ref| actor_ref.actor_addr().clone())
555 .collect::<Vec<_>>();
556 let mut expected: HashMap<&hyperactor::ActorAddr, Option<SeqInfo>> = match expected_seqs {
557 None => expected_actor_ids
558 .iter()
559 .map(|actor_id| (actor_id, None))
560 .collect(),
561 Some((session_id, seqs)) => expected_actor_ids
562 .iter()
563 .zip(
564 seqs.into_iter()
565 .map(|seq| Some(SeqInfo::Session { session_id, seq })),
566 )
567 .collect(),
568 };
569
570 while !expected.is_empty() {
571 let (actor_id, rcved) = rx.recv().await.unwrap();
572 let rcv_seq_info = rcved.unwrap();
573 let removed = expected.remove(&actor_id);
574 assert!(
575 removed.is_some(),
576 "got {actor_id}, expect {expected_actor_ids:?}"
577 );
578 if let Some(expected) = removed.unwrap() {
579 assert_eq!(expected, rcv_seq_info, "got different seq for {actor_id}");
580 }
581 }
582
583 tokio::time::sleep(Duration::from_secs(1)).await;
585 let result = rx.try_recv();
586 assert!(result.as_ref().unwrap().is_none(), "got {result:?}");
587}