1#![feature(assert_matches)]
12#![feature(exit_status_error)]
13#![feature(impl_trait_in_bindings)]
14#![feature(get_disjoint_mut_helpers)]
15#![feature(exact_size_is_empty)]
16#![feature(async_fn_track_caller)]
17#![allow(unused_assignments)]
22
23pub mod actor_mesh;
24pub mod alloc;
25mod assign;
26pub mod bootstrap;
27pub mod casting;
28pub mod comm;
29pub mod config;
30pub mod connect;
31pub mod global_context;
32pub mod host_mesh;
33pub mod introspect;
34pub mod logging;
35pub mod mesh;
36pub mod mesh_admin;
37pub mod mesh_controller;
38pub mod mesh_selection;
39mod metrics;
40pub mod namespace;
41pub mod proc_agent;
42pub mod proc_launcher;
43pub mod proc_mesh;
44pub mod reference;
45pub mod resource;
46pub mod shared_cell;
47pub mod shortuuid;
48pub mod supervision;
49#[cfg(target_os = "linux")]
50mod systemd;
51pub mod test_utils;
52pub mod testactor;
53pub mod testing;
54mod testresource;
55pub mod transport;
56pub mod value_mesh;
57
58use std::io;
59use std::str::FromStr;
60
61pub use actor_mesh::ActorMesh;
62pub use actor_mesh::ActorMeshRef;
63pub use bootstrap::Bootstrap;
64pub use bootstrap::bootstrap;
65pub use bootstrap::bootstrap_or_die;
66pub use casting::CastError;
67pub use comm::CommActor;
68pub use dashmap;
69use enum_as_inner::EnumAsInner;
70pub use global_context::GlobalClientActor;
71pub use global_context::GlobalContext;
72pub use global_context::context;
73pub use global_context::this_host;
74pub use global_context::this_proc;
75pub use host_mesh::HostMeshRef;
76use hyperactor::host::HostError;
77use hyperactor::mailbox::MailboxSenderError;
78use hyperactor::reference as hyperactor_reference;
79pub use hyperactor_mesh_macros::sel;
80pub use mesh::Mesh;
81pub use ndslice::extent;
83use ndslice::view;
84pub use proc_mesh::ProcMesh;
85pub use proc_mesh::ProcMeshRef;
86use serde::Deserialize;
87use serde::Serialize;
88use typeuri::Named;
89pub use value_mesh::ValueMesh;
90
91use crate::host_mesh::HostAgent;
92use crate::host_mesh::HostMeshRefParseError;
93use crate::host_mesh::host_agent::ProcState;
94use crate::resource::RankedValues;
95use crate::resource::Status;
96use crate::shortuuid::ShortUuid;
97use crate::supervision::MeshFailure;
98
99pub type StatusMesh = ValueMesh<Status>;
107
108pub type StatusOverlay = value_mesh::ValueOverlay<Status>;
117
118#[derive(Debug, EnumAsInner, thiserror::Error)]
120pub enum Error {
121 #[error("invalid mesh ref: expected {expected} ranks, but contains {actual} ranks")]
122 InvalidRankCardinality { expected: usize, actual: usize },
123
124 #[error(transparent)]
125 NameParseError(#[from] NameParseError),
126
127 #[error(transparent)]
128 HostMeshRefParseError(#[from] HostMeshRefParseError),
129
130 #[error(transparent)]
131 AllocatorError(#[from] Box<crate::alloc::AllocatorError>),
132
133 #[error(transparent)]
134 ChannelError(#[from] Box<hyperactor::channel::ChannelError>),
135
136 #[error(transparent)]
137 MailboxError(#[from] Box<hyperactor::mailbox::MailboxError>),
138
139 #[error(transparent)]
140 CodecError(#[from] CodecError),
141
142 #[error("error during mesh configuration: {0}")]
143 ConfigurationError(anyhow::Error),
144
145 #[error("configuration error: mesh is unroutable")]
148 UnroutableMesh(),
149
150 #[error("error while calling actor {0}: {1}")]
151 CallError(hyperactor_reference::ActorId, anyhow::Error),
152
153 #[error("actor not registered for type {0}")]
154 ActorTypeNotRegistered(String),
155
156 #[error("error while spawning actor {0}: {1}")]
158 GspawnError(Name, String),
159
160 #[error("error while sending message to actor {0}: {1}")]
161 SendingError(hyperactor_reference::ActorId, Box<MailboxSenderError>),
162
163 #[error("error while casting message to {0}: {1}")]
164 CastingError(Name, anyhow::Error),
165
166 #[error("error configuring host mesh agent {0}: {1}")]
167 HostMeshAgentConfigurationError(hyperactor_reference::ActorId, String),
168
169 #[error(
170 "error creating proc (host rank {host_rank}) on host mesh agent {mesh_agent}, state: {state}"
171 )]
172 ProcCreationError {
173 state: Box<resource::State<ProcState>>,
174 host_rank: usize,
175 mesh_agent: hyperactor_reference::ActorRef<HostAgent>,
176 },
177
178 #[error(
179 "error spawning proc mesh: statuses: {}",
180 RankedValues::invert(statuses)
181 )]
182 ProcSpawnError { statuses: RankedValues<Status> },
183
184 #[error(
185 "error spawning actor mesh: statuses: {}",
186 RankedValues::invert(statuses)
187 )]
188 ActorSpawnError { statuses: RankedValues<Status> },
189
190 #[error(
191 "error stopping actor mesh: statuses: {}",
192 RankedValues::invert(statuses)
193 )]
194 ActorStopError { statuses: RankedValues<Status> },
195
196 #[error("error spawning actor: {0}")]
197 SingletonActorSpawnError(anyhow::Error),
198
199 #[error("error spawning controller actor for mesh {0}: {1}")]
200 ControllerActorSpawnError(Name, anyhow::Error),
201
202 #[error("proc {0} must be direct-addressable")]
203 RankedProc(hyperactor_reference::ProcId),
204
205 #[error("{0}")]
206 Supervision(Box<MeshFailure>),
207
208 #[error("error: {0} does not exist")]
209 NotExist(Name),
210
211 #[error(transparent)]
212 Io(#[from] io::Error),
213
214 #[error(transparent)]
215 Host(#[from] HostError),
216
217 #[error(transparent)]
218 Other(#[from] anyhow::Error),
219}
220
221#[derive(Debug, thiserror::Error)]
223pub enum CodecError {
224 #[error(transparent)]
225 BincodeError(#[from] Box<bincode::Error>),
226 #[error(transparent)]
227 JsonError(#[from] Box<serde_json::Error>),
228 #[error(transparent)]
229 Base64Error(#[from] Box<base64::DecodeError>),
230 #[error(transparent)]
231 Utf8Error(#[from] Box<std::str::Utf8Error>),
232}
233
234impl From<bincode::Error> for Error {
235 fn from(e: bincode::Error) -> Self {
236 Error::CodecError(Box::new(e).into())
237 }
238}
239
240impl From<serde_json::Error> for Error {
241 fn from(e: serde_json::Error) -> Self {
242 Error::CodecError(Box::new(e).into())
243 }
244}
245
246impl From<base64::DecodeError> for Error {
247 fn from(e: base64::DecodeError) -> Self {
248 Error::CodecError(Box::new(e).into())
249 }
250}
251
252impl From<std::str::Utf8Error> for Error {
253 fn from(e: std::str::Utf8Error) -> Self {
254 Error::CodecError(Box::new(e).into())
255 }
256}
257
258impl From<crate::alloc::AllocatorError> for Error {
259 fn from(e: crate::alloc::AllocatorError) -> Self {
260 Error::AllocatorError(Box::new(e))
261 }
262}
263
264impl From<hyperactor::channel::ChannelError> for Error {
265 fn from(e: hyperactor::channel::ChannelError) -> Self {
266 Error::ChannelError(Box::new(e))
267 }
268}
269
270impl From<hyperactor::mailbox::MailboxError> for Error {
271 fn from(e: hyperactor::mailbox::MailboxError) -> Self {
272 Error::MailboxError(Box::new(e))
273 }
274}
275
276impl From<view::InvalidCardinality> for Error {
277 fn from(e: view::InvalidCardinality) -> Self {
278 Error::InvalidRankCardinality {
279 expected: e.expected,
280 actual: e.actual,
281 }
282 }
283}
284
285pub type Result<T> = std::result::Result<T, Error>;
287
288pub(crate) fn actor_display_name(base: &str, point: &view::Point) -> String {
293 if point.is_empty() {
294 return base.to_string();
295 }
296 let coords = point.format_as_dict();
297 if let Some(pos) = base.rfind('>') {
298 format!("{}{}{}", &base[..pos], coords, &base[pos..])
299 } else {
300 format!("{}{}", base, coords)
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Named, EnumAsInner)]
309pub enum Name {
310 Suffixed(String, ShortUuid),
312 Reserved(String),
314}
315wirevalue::register_type!(Name);
316
317static NAME_SUFFIX_DELIMITER: &str = "-";
321
322impl Name {
323 pub fn new(name: impl Into<String>) -> Result<Self> {
325 Ok(Self::new_with_uuid(name, Some(ShortUuid::generate()))?)
326 }
327
328 pub fn new_reserved(name: impl Into<String>) -> Result<Self> {
330 Ok(Self::new_with_uuid(name, None)?)
331 }
332
333 fn new_with_uuid(
334 name: impl Into<String>,
335 uuid: Option<ShortUuid>,
336 ) -> std::result::Result<Self, NameParseError> {
337 let mut name = name.into();
338 if name.is_empty() {
339 name = "unnamed".to_string();
340 }
341 if !hyperactor_reference::is_valid_ident(&name) {
342 return Err(NameParseError::InvalidName(name));
343 }
344 if let Some(uuid) = uuid {
345 Ok(Self::Suffixed(name, uuid))
346 } else {
347 Ok(Self::Reserved(name))
348 }
349 }
350
351 pub fn name(&self) -> &str {
353 match self {
354 Self::Suffixed(n, _) => n,
355 Self::Reserved(n) => n,
356 }
357 }
358
359 pub fn uuid(&self) -> Option<&ShortUuid> {
362 match self {
363 Self::Suffixed(_, uuid) => Some(uuid),
364 Self::Reserved(_) => None,
365 }
366 }
367}
368
369impl Serialize for Name {
370 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
371 where
372 S: serde::Serializer,
373 {
374 serializer.serialize_str(&self.to_string())
376 }
377}
378
379impl<'de> Deserialize<'de> for Name {
380 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
381 where
382 D: serde::Deserializer<'de>,
383 {
384 let s = String::deserialize(deserializer)?;
385 Name::from_str(&s).map_err(serde::de::Error::custom)
386 }
387}
388
389#[derive(thiserror::Error, Debug)]
391pub enum NameParseError {
392 #[error("invalid name: missing name")]
393 MissingName,
394
395 #[error("invalid name: missing uuid")]
396 MissingUuid,
397
398 #[error(
402 "invalid name '{0}': names must contain only alphanumeric characters \
403 and underscores, and must start with a letter or underscore"
404 )]
405 InvalidName(String),
406
407 #[error(transparent)]
408 InvalidUuid(#[from] <ShortUuid as FromStr>::Err),
409
410 #[error("invalid name: missing separator")]
411 MissingSeparator,
412}
413
414impl FromStr for Name {
415 type Err = NameParseError;
416
417 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
418 if let Some((name, uuid)) = s.split_once(NAME_SUFFIX_DELIMITER) {
421 if name.is_empty() {
422 return Err(NameParseError::MissingName);
423 }
424 if uuid.is_empty() {
425 return Err(NameParseError::MissingName);
426 }
427
428 Name::new_with_uuid(name.to_string(), Some(uuid.parse()?))
429 } else {
430 if s.is_empty() {
431 return Err(NameParseError::MissingName);
432 }
433 Name::new_with_uuid(s, None)
434 }
435 }
436}
437
438impl std::fmt::Display for Name {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 match self {
441 Self::Suffixed(n, uuid) => {
442 write!(f, "{}{}", n, NAME_SUFFIX_DELIMITER)?;
443 uuid.format(f, true )
444 }
445 Self::Reserved(n) => write!(f, "{}", n),
446 }
447 }
448}
449
450#[cfg(test)]
451mod tests {
452
453 #[test]
454 fn basic() {
455 use ndslice::selection::dsl;
456 use ndslice::selection::structurally_equal;
457
458 let actual = sel!(*, 0:4, *);
459 let expected = dsl::all(dsl::range(
460 ndslice::shape::Range(0, Some(4), 1),
461 dsl::all(dsl::true_()),
462 ));
463 assert!(structurally_equal(&actual, &expected));
464 }
465
466 #[cfg(FALSE)]
467 #[test]
468 fn shouldnt_compile() {
469 let _ = sel!(foobar);
470 }
471 use std::str::FromStr;
481
482 use hyperactor_mesh_macros::sel;
483 use ndslice::assert_round_trip;
484 use ndslice::assert_structurally_eq;
485 use ndslice::selection::Selection;
486
487 macro_rules! assert_round_trip_match {
488 ($left:expr, $right:expr) => {{
489 assert_structurally_eq!($left, $right);
490 assert_round_trip!($left);
491 assert_round_trip!($right);
492 }};
493 }
494
495 #[test]
496 fn token_parser() {
497 use ndslice::selection::dsl::*;
498 use ndslice::shape;
499
500 assert_round_trip_match!(all(true_()), sel!(*));
501 assert_round_trip_match!(range(3, true_()), sel!(3));
502 assert_round_trip_match!(range(1..4, true_()), sel!(1:4));
503 assert_round_trip_match!(all(range(1..4, true_())), sel!(*, 1:4));
504 assert_round_trip_match!(range(shape::Range(0, None, 1), true_()), sel!(:));
505 assert_round_trip_match!(any(true_()), sel!(?));
506 assert_round_trip_match!(any(range(1..4, all(true_()))), sel!(?, 1:4, *));
507 assert_round_trip_match!(union(range(0, true_()), range(1, true_())), sel!(0 | 1));
508 assert_round_trip_match!(
509 intersection(range(0..4, true_()), range(2..6, true_())),
510 sel!(0:4 & 2:6)
511 );
512 assert_round_trip_match!(range(shape::Range(0, None, 1), true_()), sel!(:));
513 assert_round_trip_match!(all(true_()), sel!(*));
514 assert_round_trip_match!(any(true_()), sel!(?));
515 assert_round_trip_match!(all(all(all(true_()))), sel!(*, *, *));
516 assert_round_trip_match!(intersection(all(true_()), all(true_())), sel!(* & *));
517 assert_round_trip_match!(
518 all(all(union(
519 range(0..2, true_()),
520 range(shape::Range(6, None, 1), true_())
521 ))),
522 sel!(*, *, (:2|6:))
523 );
524 assert_round_trip_match!(
525 all(all(range(shape::Range(1, None, 2), true_()))),
526 sel!(*, *, 1::2)
527 );
528 assert_round_trip_match!(
529 range(
530 shape::Range(0, Some(1), 1),
531 any(range(shape::Range(0, Some(4), 1), true_()))
532 ),
533 sel!(0, ?, :4)
534 );
535 assert_round_trip_match!(range(shape::Range(1, Some(4), 2), true_()), sel!(1:4:2));
536 assert_round_trip_match!(range(shape::Range(0, None, 2), true_()), sel!(::2));
537 assert_round_trip_match!(
538 union(range(0..4, true_()), range(4..8, true_())),
539 sel!(0:4 | 4:8)
540 );
541 assert_round_trip_match!(
542 intersection(range(0..4, true_()), range(2..6, true_())),
543 sel!(0:4 & 2:6)
544 );
545 assert_round_trip_match!(
546 all(union(range(1..4, all(true_())), range(5..6, all(true_())))),
547 sel!(*, (1:4 | 5:6), *)
548 );
549 assert_round_trip_match!(
550 range(
551 0,
552 intersection(
553 range(1..4, range(7, true_())),
554 range(2..5, range(7, true_()))
555 )
556 ),
557 sel!(0, (1:4 & 2:5), 7)
558 );
559 assert_round_trip_match!(
560 all(all(union(
561 union(range(0..2, true_()), range(4..6, true_())),
562 range(shape::Range(6, None, 1), true_())
563 ))),
564 sel!(*, *, (:2 | 4:6 | 6:))
565 );
566 assert_round_trip_match!(intersection(all(true_()), all(true_())), sel!(* & *));
567 assert_round_trip_match!(union(all(true_()), all(true_())), sel!(* | *));
568 assert_round_trip_match!(
569 intersection(
570 range(0..2, true_()),
571 union(range(1, true_()), range(2, true_()))
572 ),
573 sel!(0:2 & (1 | 2))
574 );
575 assert_round_trip_match!(
576 all(all(intersection(
577 range(1..2, true_()),
578 range(2..3, true_())
579 ))),
580 sel!(*,*,(1:2&2:3))
581 );
582 assert_round_trip_match!(
583 intersection(all(all(all(true_()))), all(all(all(true_())))),
584 sel!((*,*,*) & (*,*,*))
585 );
586 assert_round_trip_match!(
587 intersection(
588 range(0, all(all(true_()))),
589 range(0, union(range(1, all(true_())), range(3, all(true_()))))
590 ),
591 sel!((0, *, *) & (0, (1 | 3), *))
592 );
593 assert_round_trip_match!(
594 intersection(
595 range(0, all(all(true_()))),
596 range(
597 0,
598 union(
599 range(1, range(2..5, true_())),
600 range(3, range(2..5, true_()))
601 )
602 )
603 ),
604 sel!((0, *, *) & (0, (1 | 3), 2:5))
605 );
606 assert_round_trip_match!(all(true_()), sel!((*)));
607 assert_round_trip_match!(range(1..4, range(2, true_())), sel!(((1:4), 2)));
608 assert_round_trip_match!(sel!(1:4 & 5:6 | 7:8), sel!((1:4 & 5:6) | 7:8));
609 assert_round_trip_match!(
610 union(
611 intersection(all(all(true_())), all(all(true_()))),
612 all(all(true_()))
613 ),
614 sel!((*,*) & (*,*) | (*,*))
615 );
616 assert_round_trip_match!(all(true_()), sel!(*));
617 assert_round_trip_match!(sel!(((1:4))), sel!(1:4));
618 assert_round_trip_match!(sel!(*, (*)), sel!(*, *));
619 assert_round_trip_match!(
620 intersection(
621 range(0, range(1..4, true_())),
622 range(0, union(range(2, all(true_())), range(3, all(true_()))))
623 ),
624 sel!((0,1:4)&(0,(2|3),*))
625 );
626
627 assert_round_trip_match!(
630 sel!(0 & (0, (1|3), *)),
631 intersection(
632 range(0, true_()),
633 range(0, union(range(1, all(true_())), range(3, all(true_()))))
634 )
635 );
636 assert_round_trip_match!(
637 sel!(0 & (0, (3|1), *)),
638 intersection(
639 range(0, true_()),
640 range(0, union(range(3, all(true_())), range(1, all(true_()))))
641 )
642 );
643 assert_round_trip_match!(
644 sel!((*, *, *) & (*, *, (2 | 4))),
645 intersection(
646 all(all(all(true_()))),
647 all(all(union(range(2, true_()), range(4, true_()))))
648 )
649 );
650 assert_round_trip_match!(
651 sel!((*, *, *) & (*, *, (4 | 2))),
652 intersection(
653 all(all(all(true_()))),
654 all(all(union(range(4, true_()), range(2, true_()))))
655 )
656 );
657 assert_round_trip_match!(
658 sel!((*, (1|2)) & (*, (2|1))),
659 intersection(
660 all(union(range(1, true_()), range(2, true_()))),
661 all(union(range(2, true_()), range(1, true_())))
662 )
663 );
664 assert_round_trip_match!(
665 sel!((*, *, *) & *),
666 intersection(all(all(all(true_()))), all(true_()))
667 );
668 assert_round_trip_match!(
669 sel!(* & (*, *, *)),
670 intersection(all(true_()), all(all(all(true_()))))
671 );
672
673 assert_round_trip_match!(
674 sel!( (*, *, *) & ((*, *, *) & (*, *, *)) ),
675 intersection(
676 all(all(all(true_()))),
677 intersection(all(all(all(true_()))), all(all(all(true_()))))
678 )
679 );
680 assert_round_trip_match!(
681 sel!((1, *, *) | (0 & (0, 3, *))),
682 union(
683 range(1, all(all(true_()))),
684 intersection(range(0, true_()), range(0, range(3, all(true_()))))
685 )
686 );
687 assert_round_trip_match!(
688 sel!(((0, *)| (1, *)) & ((1, *) | (0, *))),
689 intersection(
690 union(range(0, all(true_())), range(1, all(true_()))),
691 union(range(1, all(true_())), range(0, all(true_())))
692 )
693 );
694 assert_round_trip_match!(sel!(*, 8:8), all(range(8..8, true_())));
695 assert_round_trip_match!(
696 sel!((*, 1) & (*, 8 : 8)),
697 intersection(all(range(1..2, true_())), all(range(8..8, true_())))
698 );
699 assert_round_trip_match!(
700 sel!((*, 8 : 8) | (*, 1)),
701 union(all(range(8..8, true_())), all(range(1..2, true_())))
702 );
703 assert_round_trip_match!(
704 sel!((*, 1) | (*, 2:8)),
705 union(all(range(1..2, true_())), all(range(2..8, true_())))
706 );
707 assert_round_trip_match!(
708 sel!((*, *, *) & (*, *, 2:8)),
709 intersection(all(all(all(true_()))), all(all(range(2..8, true_()))))
710 );
711 }
712
713 #[test]
714 fn test_name_unique() {
715 use super::Name;
716 assert_ne!(Name::new("foo").unwrap(), Name::new("foo").unwrap());
717 let name = Name::new("foo").unwrap();
718 assert_eq!(name, name);
719 }
720
721 #[test]
722 fn test_name_roundtrip() {
723 use super::Name;
724 use super::ShortUuid;
725 let uuid = "111111111111".parse::<ShortUuid>().unwrap();
726 let name = Name::new_with_uuid("foo", Some(uuid)).unwrap();
727 let str = name.to_string();
728 assert_eq!(str, "foo-111111111111");
729 assert_eq!(name, Name::from_str(&str).unwrap());
730 }
731
732 #[test]
733 fn test_name_roundtrip_with_underscore() {
734 use super::Name;
735 use super::ShortUuid;
736 let uuid = "_1a2b3c4d5e6f".parse::<ShortUuid>().unwrap();
739 let name = Name::new_with_uuid("foo", Some(uuid)).unwrap();
740 let str = name.to_string();
741 assert_eq!(str, "foo-1a2b3c4d5e6f");
743 assert_eq!(name, Name::from_str(&str).unwrap());
744 }
745
746 #[test]
747 fn test_name_roundtrip_random() {
748 use super::Name;
749 let name = Name::new("foo").unwrap();
750 assert_eq!(name, Name::from_str(&name.to_string()).unwrap());
751 }
752
753 #[test]
754 fn test_name_roundtrip_reserved() {
755 use super::Name;
756 let name = Name::new_reserved("foo").unwrap();
757 let str = name.to_string();
758 assert_eq!(str, "foo");
759 assert_eq!(name, Name::from_str(&str).unwrap());
760 }
761
762 #[test]
763 fn test_name_parse() {
764 use super::Name;
765 let name = Name::from_str("foo_bar_1a2b3c4d5e6f").unwrap();
768 assert_eq!(format!("{}", name), "foo_bar_1a2b3c4d5e6f");
769 }
770
771 #[test]
772 fn test_invalid() {
773 use super::Name;
774 assert!(Name::new("").is_ok());
776 assert!(Name::new("foo-").is_err());
778 assert!(Name::new("foo-bar").is_err());
779 }
780}