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}