pub mod app {
pub use crate::app::{
AppCompileError, AppHandle, AppSpawnError, AppSpec, AppStartError, AppStopError,
CompiledApp, StoppedApp,
};
}
pub mod supervisor {
pub use crate::supervision::{
BackoffStrategy, ChildName, ChildSpec, ChildStart, CompiledSupervisor, EscalationPolicy,
RestartConfig, RestartPolicy, StartTieBreak, StartedChild, SupervisionStrategy,
SupervisorBuilder, SupervisorCompileError, SupervisorHandle, SupervisorSpawnError,
};
}
pub mod gen_server {
pub use crate::gen_server::{
CallError, CastError, CastOverflowPolicy, DownMsg, ExitMsg, GenServer, GenServerHandle,
GenServerRef, InfoError, NamedGenServerStart, Reply, ReplyOutcome, SystemMsg, TimeoutMsg,
named_gen_server_start,
};
}
pub mod registry {
pub use crate::cx::registry::{
GrantedLease, NameCollisionOutcome, NameCollisionPolicy, NameLease, NameLeaseError,
NameOwnershipKind, NameOwnershipNotification, NamePermit, NameRegistry, NameWatchRef,
RegistryCap, RegistryEvent, RegistryHandle,
};
}
pub mod monitor {
pub use crate::monitor::{DownNotification, DownReason, MonitorRef};
}
pub mod link {
pub use crate::link::{ExitPolicy, ExitSignal, LinkRef};
}
pub mod crash {
pub use crate::trace::crashpack::{
ArtifactId, CrashPack, CrashPackConfig, CrashPackManifest, CrashPackWriteError,
CrashPackWriter, FailureInfo, FailureOutcome, FileCrashPackWriter, MemoryCrashPackWriter,
ReplayCommand,
};
}
pub mod prelude {
pub use crate::app::{AppHandle, AppSpec, StoppedApp};
pub use crate::supervision::{
BackoffStrategy, ChildName, ChildSpec, ChildStart, RestartConfig, RestartPolicy,
SupervisionStrategy, SupervisorBuilder,
};
pub use crate::gen_server::{
CallError, CastError, DownMsg, ExitMsg, GenServer, GenServerHandle, NamedGenServerStart,
Reply, SystemMsg, TimeoutMsg, named_gen_server_start,
};
pub use crate::cx::{NameLease, NameRegistry, RegistryHandle};
pub use crate::monitor::{DownNotification, DownReason, MonitorRef};
pub use crate::link::{ExitPolicy, ExitSignal, LinkRef};
pub use crate::app::{AppCompileError, AppStartError};
pub use crate::supervision::SupervisorCompileError;
pub use super::error::SporkError;
}
pub mod error {
use crate::app::{AppCompileError, AppSpawnError, AppStartError, AppStopError};
use crate::gen_server::{CallError, CastError, InfoError};
use crate::runtime::{RegionCreateError, SpawnError};
use crate::supervision::{SupervisorCompileError, SupervisorSpawnError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SporkSeverity {
Transient,
Permanent,
}
#[derive(Debug)]
pub enum SporkError {
Start(AppStartError),
Stop(AppStopError),
Compile(AppCompileError),
Spawn(AppSpawnError),
Call(CallError),
Cast(CastError),
Info(InfoError),
}
impl SporkError {
fn region_create_severity(error: &RegionCreateError) -> SporkSeverity {
match error {
RegionCreateError::ParentAtCapacity { .. } => SporkSeverity::Transient,
RegionCreateError::ParentNotFound(_) | RegionCreateError::ParentClosed(_) => {
SporkSeverity::Permanent
}
RegionCreateError::ResourcePressure { .. } => SporkSeverity::Transient,
}
}
fn runtime_spawn_severity(error: &SpawnError) -> SporkSeverity {
match error {
SpawnError::RegionAtCapacity { .. } => SporkSeverity::Transient,
SpawnError::RuntimeUnavailable
| SpawnError::RegionNotFound(_)
| SpawnError::RegionClosed(_)
| SpawnError::LocalSchedulerUnavailable
| SpawnError::NameRegistrationFailed { .. } => SporkSeverity::Permanent,
}
}
fn supervisor_spawn_severity(error: &SupervisorSpawnError) -> SporkSeverity {
match error {
SupervisorSpawnError::RegionCreate(error) => Self::region_create_severity(error),
SupervisorSpawnError::ChildStartFailed { err, .. } => {
Self::runtime_spawn_severity(err)
}
SupervisorSpawnError::DependencyUnavailable {
dependency_error, ..
} => dependency_error
.as_ref()
.map_or(SporkSeverity::Permanent, Self::runtime_spawn_severity),
}
}
fn app_spawn_severity(error: &AppSpawnError) -> SporkSeverity {
match error {
AppSpawnError::RegionCreate(error) => Self::region_create_severity(error),
AppSpawnError::SpawnFailed(error) => Self::supervisor_spawn_severity(error),
}
}
#[must_use]
pub fn severity(&self) -> SporkSeverity {
match self {
Self::Start(AppStartError::CompileFailed(_)) | Self::Stop(_) | Self::Compile(_) => {
SporkSeverity::Permanent
}
Self::Start(AppStartError::SpawnFailed(error)) | Self::Spawn(error) => {
Self::app_spawn_severity(error)
}
Self::Call(e) => match e {
CallError::ServerStopped | CallError::NoReply | CallError::Cancelled(_) => {
SporkSeverity::Permanent
}
},
Self::Cast(e) => match e {
CastError::Full => SporkSeverity::Transient,
CastError::ServerStopped | CastError::Cancelled(_) => SporkSeverity::Permanent,
},
Self::Info(e) => match e {
InfoError::Full => SporkSeverity::Transient,
InfoError::ServerStopped | InfoError::Cancelled(_) => SporkSeverity::Permanent,
},
}
}
#[must_use]
pub fn is_permanent(&self) -> bool {
self.severity() == SporkSeverity::Permanent
}
#[must_use]
pub fn is_transient(&self) -> bool {
self.severity() == SporkSeverity::Transient
}
#[must_use]
pub fn domain(&self) -> &'static str {
match self {
Self::Start(_) => "start",
Self::Stop(_) => "stop",
Self::Compile(_) => "compile",
Self::Spawn(_) => "spawn",
Self::Call(_) => "call",
Self::Cast(_) => "cast",
Self::Info(_) => "info",
}
}
}
impl std::fmt::Display for SporkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Start(e) => write!(f, "spork start: {e}"),
Self::Stop(e) => write!(f, "spork stop: {e}"),
Self::Compile(e) => write!(f, "spork compile: {e}"),
Self::Spawn(e) => write!(f, "spork spawn: {e}"),
Self::Call(e) => write!(f, "spork call: {e}"),
Self::Cast(e) => write!(f, "spork cast: {e}"),
Self::Info(e) => write!(f, "spork info: {e}"),
}
}
}
impl std::error::Error for SporkError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Start(e) => Some(e),
Self::Stop(e) => Some(e),
Self::Compile(e) => Some(e),
Self::Spawn(e) => Some(e),
Self::Call(e) => Some(e),
Self::Cast(e) => Some(e),
Self::Info(e) => Some(e),
}
}
}
impl From<AppStartError> for SporkError {
fn from(e: AppStartError) -> Self {
Self::Start(e)
}
}
impl From<AppStopError> for SporkError {
fn from(e: AppStopError) -> Self {
Self::Stop(e)
}
}
impl From<AppCompileError> for SporkError {
fn from(e: AppCompileError) -> Self {
Self::Compile(e)
}
}
impl From<AppSpawnError> for SporkError {
fn from(e: AppSpawnError) -> Self {
Self::Spawn(e)
}
}
impl From<SupervisorCompileError> for SporkError {
fn from(e: SupervisorCompileError) -> Self {
Self::Compile(AppCompileError::SupervisorCompile(e))
}
}
impl From<SupervisorSpawnError> for SporkError {
fn from(e: SupervisorSpawnError) -> Self {
Self::Spawn(AppSpawnError::SpawnFailed(e))
}
}
impl From<CallError> for SporkError {
fn from(e: CallError) -> Self {
Self::Call(e)
}
}
impl From<CastError> for SporkError {
fn from(e: CastError) -> Self {
Self::Cast(e)
}
}
impl From<InfoError> for SporkError {
fn from(e: InfoError) -> Self {
Self::Info(e)
}
}
}
#[cfg(test)]
#[allow(clippy::no_effect_underscore_binding)]
mod tests {
use super::*;
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn prelude_imports_compile() {
init_test("prelude_imports_compile");
let _ = std::any::type_name::<prelude::AppSpec>();
let _ = std::any::type_name::<prelude::AppHandle>();
let _ = std::any::type_name::<prelude::StoppedApp>();
let _ = std::any::type_name::<prelude::SupervisorBuilder>();
let _ = std::any::type_name::<prelude::ChildSpec>();
let _ = std::any::type_name::<prelude::RestartConfig>();
let _ = std::any::type_name::<prelude::SupervisionStrategy>();
let _ = std::any::type_name::<prelude::RestartPolicy>();
let _ = std::any::type_name::<prelude::BackoffStrategy>();
let _ = std::any::type_name::<prelude::NameRegistry>();
let _ = std::any::type_name::<prelude::RegistryHandle>();
let _ = std::any::type_name::<prelude::NameLease>();
let _ = std::any::type_name::<prelude::MonitorRef>();
let _ = std::any::type_name::<prelude::DownReason>();
let _ = std::any::type_name::<prelude::DownNotification>();
let _ = std::any::type_name::<prelude::DownMsg>();
let _ = std::any::type_name::<prelude::ExitMsg>();
let _ = std::any::type_name::<prelude::TimeoutMsg>();
let _ = std::any::type_name::<prelude::ExitPolicy>();
let _ = std::any::type_name::<prelude::LinkRef>();
let _ = std::any::type_name::<prelude::CallError>();
let _ = std::any::type_name::<prelude::CastError>();
let _ = std::any::type_name::<prelude::AppStartError>();
let _ = std::any::type_name::<prelude::AppCompileError>();
let _ = std::any::type_name::<prelude::SupervisorCompileError>();
crate::test_complete!("prelude_imports_compile");
}
#[test]
fn submodule_types_accessible() {
init_test("submodule_types_accessible");
let _ = std::any::type_name::<app::CompiledApp>();
let _ = std::any::type_name::<app::AppSpawnError>();
let _ = std::any::type_name::<app::AppStopError>();
let _ = std::any::type_name::<supervisor::CompiledSupervisor>();
let _ = std::any::type_name::<supervisor::EscalationPolicy>();
let _ = std::any::type_name::<supervisor::StartTieBreak>();
let _ = std::any::type_name::<supervisor::SupervisorHandle>();
let _ = std::any::type_name::<supervisor::StartedChild>();
let _ = std::any::type_name::<supervisor::SupervisorSpawnError>();
let _ = std::any::type_name::<gen_server::CastOverflowPolicy>();
let _ = std::any::type_name::<gen_server::InfoError>();
let _ = std::any::type_name::<gen_server::ReplyOutcome>();
let _ = std::any::type_name::<gen_server::DownMsg>();
let _ = std::any::type_name::<gen_server::ExitMsg>();
let _ = std::any::type_name::<gen_server::TimeoutMsg>();
let _ = std::any::type_name::<registry::NameRegistry>();
let _ = std::any::type_name::<registry::RegistryHandle>();
let _ = std::any::type_name::<registry::NameLease>();
let _ = std::any::type_name::<registry::NameCollisionPolicy>();
let _ = std::any::type_name::<monitor::MonitorRef>();
let _ = std::any::type_name::<link::ExitPolicy>();
let _ = std::any::type_name::<crash::CrashPack>();
let _ = std::any::type_name::<crash::CrashPackConfig>();
let _ = std::any::type_name::<crash::ReplayCommand>();
crate::test_complete!("submodule_types_accessible");
}
#[test]
fn supervision_strategy_constructible() {
init_test("supervision_strategy_constructible");
let _stop = prelude::SupervisionStrategy::Stop;
let _restart = prelude::SupervisionStrategy::Restart(prelude::RestartConfig::default());
let _escalate = prelude::SupervisionStrategy::Escalate;
let _one_for_one = prelude::RestartPolicy::OneForOne;
let _one_for_all = prelude::RestartPolicy::OneForAll;
let _rest_for_one = prelude::RestartPolicy::RestForOne;
let _none = prelude::BackoffStrategy::None;
crate::test_complete!("supervision_strategy_constructible");
}
#[test]
fn down_reason_constructible() {
init_test("down_reason_constructible");
let _normal = prelude::DownReason::Normal;
let _error = prelude::DownReason::Error("oops".to_string());
crate::test_complete!("down_reason_constructible");
}
#[test]
fn exit_policy_constructible() {
init_test("exit_policy_constructible");
let _prop = prelude::ExitPolicy::Propagate;
let _trap = prelude::ExitPolicy::Trap;
let _ignore = prelude::ExitPolicy::Ignore;
crate::test_complete!("exit_policy_constructible");
}
mod error_taxonomy {
use crate::app::{AppCompileError, AppSpawnError, AppStartError, AppStopError};
use crate::gen_server::{CallError, CastError, InfoError};
use crate::runtime::{RegionCreateError, SpawnError};
use crate::spork::error::{SporkError, SporkSeverity};
use crate::supervision::{SupervisorCompileError, SupervisorSpawnError};
use crate::types::RegionId;
use crate::util::arena::ArenaIndex;
fn dummy_region_id() -> RegionId {
RegionId::from_arena(ArenaIndex::new(0, 0))
}
fn parent_capacity_error(region: RegionId) -> RegionCreateError {
RegionCreateError::ParentAtCapacity {
region,
limit: 1,
live: 1,
}
}
fn region_task_capacity_error(region: RegionId) -> SpawnError {
SpawnError::RegionAtCapacity {
region,
limit: 1,
live: 1,
}
}
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn from_call_error() {
init_test("from_call_error");
let e: SporkError = CallError::ServerStopped.into();
assert!(matches!(e, SporkError::Call(CallError::ServerStopped)));
crate::test_complete!("from_call_error");
}
#[test]
fn from_cast_error() {
init_test("from_cast_error");
let e: SporkError = CastError::Full.into();
assert!(matches!(e, SporkError::Cast(CastError::Full)));
crate::test_complete!("from_cast_error");
}
#[test]
fn from_info_error() {
init_test("from_info_error");
let e: SporkError = InfoError::ServerStopped.into();
assert!(matches!(e, SporkError::Info(InfoError::ServerStopped)));
crate::test_complete!("from_info_error");
}
#[test]
fn from_app_compile_error() {
init_test("from_app_compile_error");
let inner = AppCompileError::SupervisorCompile(
SupervisorCompileError::DuplicateChildName("dup".into()),
);
let e: SporkError = inner.into();
assert!(matches!(e, SporkError::Compile(_)));
crate::test_complete!("from_app_compile_error");
}
#[test]
fn from_supervisor_compile_error() {
init_test("from_supervisor_compile_error");
let inner = SupervisorCompileError::DuplicateChildName("x".into());
let e: SporkError = inner.into();
assert!(matches!(
e,
SporkError::Compile(AppCompileError::SupervisorCompile(_))
));
crate::test_complete!("from_supervisor_compile_error");
}
#[test]
fn from_app_start_error() {
init_test("from_app_start_error");
let inner = AppStartError::CompileFailed(AppCompileError::SupervisorCompile(
SupervisorCompileError::DuplicateChildName("a".into()),
));
let e: SporkError = inner.into();
assert!(matches!(e, SporkError::Start(_)));
crate::test_complete!("from_app_start_error");
}
#[test]
fn from_app_stop_error() {
init_test("from_app_stop_error");
let inner = AppStopError::RegionNotFound(dummy_region_id());
let e: SporkError = inner.into();
assert!(matches!(e, SporkError::Stop(_)));
crate::test_complete!("from_app_stop_error");
}
#[test]
fn severity_permanent_lifecycle() {
init_test("severity_permanent_lifecycle");
let e = SporkError::Start(AppStartError::CompileFailed(
AppCompileError::SupervisorCompile(SupervisorCompileError::DuplicateChildName(
"a".into(),
)),
));
assert_eq!(e.severity(), SporkSeverity::Permanent);
assert!(e.is_permanent());
assert!(!e.is_transient());
crate::test_complete!("severity_permanent_lifecycle");
}
#[test]
fn severity_permanent_call() {
init_test("severity_permanent_call");
let e = SporkError::Call(CallError::ServerStopped);
assert_eq!(e.severity(), SporkSeverity::Permanent);
assert!(e.is_permanent());
crate::test_complete!("severity_permanent_call");
}
#[test]
fn severity_transient_cast_full() {
init_test("severity_transient_cast_full");
let e = SporkError::Cast(CastError::Full);
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
assert!(!e.is_permanent());
crate::test_complete!("severity_transient_cast_full");
}
#[test]
fn severity_transient_info_full() {
init_test("severity_transient_info_full");
let e = SporkError::Info(InfoError::Full);
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
crate::test_complete!("severity_transient_info_full");
}
#[test]
fn severity_permanent_cast_stopped() {
init_test("severity_permanent_cast_stopped");
let e = SporkError::Cast(CastError::ServerStopped);
assert_eq!(e.severity(), SporkSeverity::Permanent);
crate::test_complete!("severity_permanent_cast_stopped");
}
#[test]
fn severity_transient_spawn_parent_capacity() {
init_test("severity_transient_spawn_parent_capacity");
let region = dummy_region_id();
let e = SporkError::Spawn(AppSpawnError::RegionCreate(parent_capacity_error(region)));
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
crate::test_complete!("severity_transient_spawn_parent_capacity");
}
#[test]
fn severity_transient_start_parent_capacity() {
init_test("severity_transient_start_parent_capacity");
let region = dummy_region_id();
let e = SporkError::Start(AppStartError::SpawnFailed(AppSpawnError::RegionCreate(
parent_capacity_error(region),
)));
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
crate::test_complete!("severity_transient_start_parent_capacity");
}
#[test]
fn severity_transient_spawn_child_start_region_capacity() {
init_test("severity_transient_spawn_child_start_region_capacity");
let region = dummy_region_id();
let e = SporkError::Spawn(AppSpawnError::SpawnFailed(
SupervisorSpawnError::ChildStartFailed {
child: "worker".into(),
err: region_task_capacity_error(region),
region,
},
));
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
crate::test_complete!("severity_transient_spawn_child_start_region_capacity");
}
#[test]
fn severity_transient_spawn_dependency_unavailable_preserves_root_cause() {
init_test("severity_transient_spawn_dependency_unavailable_preserves_root_cause");
let region = dummy_region_id();
let e = SporkError::Spawn(AppSpawnError::SpawnFailed(
SupervisorSpawnError::DependencyUnavailable {
child: "api".into(),
dependency: "db".into(),
dependency_error: Some(region_task_capacity_error(region)),
region,
},
));
assert_eq!(e.severity(), SporkSeverity::Transient);
assert!(e.is_transient());
crate::test_complete!(
"severity_transient_spawn_dependency_unavailable_preserves_root_cause"
);
}
#[test]
fn domain_tags() {
init_test("domain_tags");
assert_eq!(
SporkError::Start(AppStartError::CompileFailed(
AppCompileError::SupervisorCompile(SupervisorCompileError::DuplicateChildName(
"a".into()
))
))
.domain(),
"start"
);
assert_eq!(
SporkError::Stop(AppStopError::RegionNotFound(dummy_region_id())).domain(),
"stop"
);
assert_eq!(
SporkError::Compile(AppCompileError::SupervisorCompile(
SupervisorCompileError::DuplicateChildName("a".into())
))
.domain(),
"compile"
);
assert_eq!(SporkError::Call(CallError::ServerStopped).domain(), "call");
assert_eq!(SporkError::Cast(CastError::Full).domain(), "cast");
assert_eq!(SporkError::Info(InfoError::ServerStopped).domain(), "info");
crate::test_complete!("domain_tags");
}
#[test]
fn display_format() {
init_test("display_format");
let e = SporkError::Call(CallError::ServerStopped);
let s = format!("{e}");
assert!(s.starts_with("spork call:"), "got: {s}");
let e2 = SporkError::Cast(CastError::Full);
let s2 = format!("{e2}");
assert!(s2.starts_with("spork cast:"), "got: {s2}");
crate::test_complete!("display_format");
}
#[test]
fn error_source_chain() {
init_test("error_source_chain");
let e = SporkError::Call(CallError::NoReply);
let source = std::error::Error::source(&e);
assert!(source.is_some(), "SporkError should have a source");
crate::test_complete!("error_source_chain");
}
#[test]
fn severity_ordering() {
init_test("severity_ordering");
assert!(SporkSeverity::Transient < SporkSeverity::Permanent);
crate::test_complete!("severity_ordering");
}
}
}