Skip to main content

frame_core/
error.rs

1//! Typed registration and lifecycle failures.
2
3use beamr::scheduler::MailboxSendError;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::capability::{CapabilityRequest, ScopeValidationError};
8use crate::component::ComponentId;
9use crate::event::LifecycleState;
10
11/// Stable classification of a beamr process tombstone.
12#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
13pub enum TombstoneKind {
14    /// Process completed normally.
15    Normal,
16    /// Host requested an untrappable kill.
17    Kill,
18    /// Process observed a kill and terminated.
19    Killed,
20    /// Runtime execution failed.
21    Error,
22    /// A linked distributed node disconnected.
23    NoConnection,
24    /// A link targeted a process that was already absent.
25    NoProcess,
26}
27
28/// Observable reason a component entered [`LifecycleState::Failed`].
29#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
30pub struct FailureReason {
31    /// Child associated with the failure, if a child had been identified.
32    pub child: Option<String>,
33    /// Tombstone observed for the failing process, when one existed.
34    pub tombstone: Option<TombstoneKind>,
35    /// Runtime detail; rich exception information is intentionally optional.
36    pub detail: String,
37}
38
39/// A typed refusal or runtime failure from the component registry.
40#[derive(Debug, Error)]
41pub enum RegistryError {
42    /// Registration attempted to reuse a stable identity.
43    #[error("component {id} is already registered")]
44    DuplicateComponent {
45        /// Conflicting stable identity.
46        id: ComponentId,
47    },
48    /// A declared dependency was not in the registration table.
49    #[error("component {component} requires missing component {required}")]
50    MissingDependency {
51        /// Component being registered.
52        component: ComponentId,
53        /// Missing edge target.
54        required: ComponentId,
55    },
56    /// A dependency edge would make the graph cyclic.
57    #[error("dependency edge {component} -> {required} creates a cycle")]
58    CyclicDependency {
59        /// Source of the cyclic edge.
60        component: ComponentId,
61        /// Target of the cyclic edge.
62        required: ComponentId,
63    },
64    /// Two registered components claimed the same capability identifier.
65    #[error("capability {capability} is already provided by component {provider}")]
66    CapabilityConflict {
67        /// Colliding service capability identifier.
68        capability: String,
69        /// Existing provider.
70        provider: ComponentId,
71    },
72    /// A host-authority declaration used a malformed typed scope.
73    #[error("component {component} declares invalid capability need {request:?}: {reason}")]
74    InvalidCapabilityScope {
75        /// Component being registered.
76        component: ComponentId,
77        /// Offending declaration.
78        request: CapabilityRequest,
79        /// Exact scope validation refusal.
80        reason: ScopeValidationError,
81    },
82    /// A manifest repeated an exact kind-and-scope pair.
83    #[error("component {component} declares capability need {request:?} more than once")]
84    DuplicateCapabilityNeed {
85        /// Component being registered.
86        component: ComponentId,
87        /// Duplicate declaration.
88        request: CapabilityRequest,
89    },
90    /// Registration omitted the mandatory restart intensity declaration.
91    #[error("component {id} has no declared supervision intensity")]
92    UndeclaredSupervision {
93        /// Component missing the declaration.
94        id: ComponentId,
95    },
96    /// A restart window of zero cannot define an intensity interval.
97    #[error("component {id} declares a zero-length supervision window")]
98    InvalidSupervisionWindow {
99        /// Component with the invalid policy.
100        id: ComponentId,
101    },
102    /// Child names are component-local unique keys.
103    #[error("component {id} declares child name {child} more than once")]
104    DuplicateChild {
105        /// Component with the duplicate declaration.
106        id: ComponentId,
107        /// Duplicate child key.
108        child: String,
109    },
110    /// The requested component is not registered.
111    #[error("component {id} is not registered")]
112    NotRegistered {
113        /// Requested identity.
114        id: ComponentId,
115    },
116    /// A start is already in flight for this identity.
117    #[error("component {id} is already starting")]
118    AlreadyStarting {
119        /// Busy identity.
120        id: ComponentId,
121    },
122    /// The component is already running.
123    #[error("component {id} is already running")]
124    AlreadyRunning {
125        /// Running identity.
126        id: ComponentId,
127    },
128    /// A stop is already in flight for this identity.
129    #[error("component {id} is already stopping")]
130    AlreadyStopping {
131        /// Busy identity.
132        id: ComponentId,
133    },
134    /// A remove is already in flight for this identity.
135    #[error("component {id} is already being removed")]
136    AlreadyRemoving {
137        /// Busy identity.
138        id: ComponentId,
139    },
140    /// A dependency exists but has not reached Running.
141    #[error("component {component} requires {required} to be Running, found {state:?}")]
142    DependencyNotRunning {
143        /// Component whose start was refused.
144        component: ComponentId,
145        /// Required component.
146        required: ComponentId,
147        /// Observed dependency state.
148        state: LifecycleState,
149    },
150    /// A running component still depends on the removal target.
151    #[error("running component {dependent} depends on {required}")]
152    RunningDependent {
153        /// Running dependent preventing removal.
154        dependent: ComponentId,
155        /// Requested removal target.
156        required: ComponentId,
157    },
158    /// Hot-loaded bytecode carries `erlang:*` imports the scheduler composition
159    /// left Deferred — dispatch would kill the process at first use.
160    #[error(
161        "component {id} module {module} defers built-in imports [{imports}]: the scheduler was \
162         composed without a populated BIF registry; compose through \
163         frame_core::composition::compose_scheduler, never bare Scheduler::with_services"
164    )]
165    DeferredBifImports {
166        /// Component whose start was refused.
167        id: ComponentId,
168        /// Module whose committed import table carries the deferrals.
169        module: String,
170        /// Deferred imports in `erlang:name/arity` form.
171        imports: String,
172    },
173    /// beamr refused or could not parse component bytecode.
174    #[error("failed to load bytecode for component {id}: {detail}")]
175    ModuleLoad {
176        /// Component being started.
177        id: ComponentId,
178        /// beamr loader detail.
179        detail: String,
180    },
181    /// A native supervisor or linked child could not be spawned.
182    #[error("failed to spawn {process} for component {id}: {detail}")]
183    Spawn {
184        /// Component being started or restarted.
185        id: ComponentId,
186        /// Supervisor or child name.
187        process: String,
188        /// beamr spawn detail.
189        detail: String,
190    },
191    /// A child did not answer its declared mailbox probe in time.
192    #[error("component {id} child {child} liveness probe timed out")]
193    LivenessTimeout {
194        /// Component being started or probed.
195        id: ComponentId,
196        /// Child that did not answer.
197        child: String,
198    },
199    /// Start failed after entering Starting; status remains Failed.
200    #[error("component {id} failed during start: {reason:?}")]
201    StartFailed {
202        /// Failed identity.
203        id: ComponentId,
204        /// Observable reason retained in status.
205        reason: FailureReason,
206    },
207    /// Ordered stop observed a non-normal or missing tombstone.
208    #[error("component {id} failed during ordered stop: {reason:?}")]
209    StopFailed {
210        /// Component being stopped.
211        id: ComponentId,
212        /// Exact observed failure.
213        reason: FailureReason,
214    },
215    /// A retained old module generation could not be safely purged.
216    #[error("failed to purge module {module} for component {id}: {detail}")]
217    ModulePurge {
218        /// Component being removed.
219        id: ComponentId,
220        /// Module display name.
221        module: String,
222        /// beamr purge detail.
223        detail: String,
224    },
225    /// The current module generation was absent when removal expected it.
226    #[error("module {module} for component {id} was not present during delete")]
227    ModuleDelete {
228        /// Component being removed.
229        id: ComponentId,
230        /// Module display name.
231        module: String,
232    },
233    /// Lookup still found code after deletion.
234    #[error("module {module} for component {id} remained visible after delete")]
235    ModuleStillLoaded {
236        /// Component being removed.
237        id: ComponentId,
238        /// Module display name.
239        module: String,
240    },
241    /// beamr refused a typed host command at supervisor mailbox admission.
242    #[error("component {id} supervisor {command} command delivery failed: {source}")]
243    CommandDelivery {
244        /// Affected component.
245        id: ComponentId,
246        /// Stable command kind whose delivery was attempted.
247        command: &'static str,
248        /// Exact beamr mailbox admission refusal.
249        #[source]
250        source: MailboxSendError,
251    },
252    /// Host/supervisor mailbox protocol was violated.
253    #[error("component {id} supervisor protocol failed: {detail}")]
254    SupervisorProtocol {
255        /// Affected component.
256        id: ComponentId,
257        /// Protocol detail.
258        detail: String,
259    },
260    /// Internal synchronization was poisoned by a panic.
261    #[error("component registry synchronization is poisoned")]
262    SynchronizationPoisoned,
263    /// A component monitor thread terminated without returning its result.
264    #[error("component {id} monitor thread terminated unexpectedly")]
265    MonitorTerminated {
266        /// Component whose monitor was lost.
267        id: ComponentId,
268    },
269}