1use std::collections::{HashMap, HashSet};
4use std::num::NonZeroUsize;
5use std::sync::{Arc, Mutex};
6
7use beamr::atom::Atom;
8use beamr::namespace::NamespaceId;
9use beamr::scheduler::Scheduler;
10
11use crate::capability::{
12 Capability, CapabilityCheckError, CapabilityChecker, CapabilityMutationError, CapabilityTable,
13 CheckVerdict, HostCapabilityFacade,
14};
15use crate::component::{ComponentId, ComponentMeta};
16use crate::error::{FailureReason, RegistryError};
17use crate::event::{EventHub, LifecycleState, LifecycleSubscription};
18use crate::supervision::{LifecycleConfig, SharedStatus, StatusState, TreeHandle, start_tree};
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum StopOutcome {
23 Stopped,
25 AlreadyStopped,
27}
28
29#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum RemoveOutcome {
32 Removed,
34 RemovedAfterForcedCleanup(ForcedCleanupReport),
36 AlreadyRemoved,
38}
39
40pub struct ComponentRegistry {
42 scheduler: Arc<Scheduler>,
43 config: LifecycleConfig,
44 records: Mutex<HashMap<ComponentId, Arc<ComponentRecord>>>,
45 capabilities: CapabilityTable,
46 events: Arc<EventHub>,
47 incarnation_history: Mutex<HashMap<ComponentId, u64>>,
65}
66
67impl ComponentRegistry {
68 #[must_use]
73 pub fn new(scheduler: Arc<Scheduler>, config: LifecycleConfig) -> Self {
74 Self {
75 scheduler,
76 config,
77 records: Mutex::new(HashMap::new()),
78 capabilities: CapabilityTable::new(),
79 events: Arc::new(EventHub::default()),
80 incarnation_history: Mutex::new(HashMap::new()),
81 }
82 }
83
84 pub fn subscribe(
93 &self,
94 capacity: NonZeroUsize,
95 ) -> Result<LifecycleSubscription, RegistryError> {
96 self.events
97 .subscribe(capacity)
98 .map_err(|_| RegistryError::SynchronizationPoisoned)
99 }
100
101 #[must_use]
106 pub fn host_capabilities(&self) -> HostCapabilityFacade<'_> {
107 HostCapabilityFacade::new(&self.capabilities, Arc::clone(&self.events))
108 }
109
110 pub fn capability_checker(
116 &self,
117 component_id: ComponentId,
118 ) -> Result<CapabilityChecker, CapabilityMutationError> {
119 self.host_capabilities().checker(component_id)
120 }
121
122 pub fn check_capability(
131 &self,
132 component_id: ComponentId,
133 capability: &Capability,
134 ) -> Result<CheckVerdict, CapabilityCheckError> {
135 let checker = self
136 .capability_checker(component_id)
137 .map_err(|error| match error {
138 CapabilityMutationError::NotRegistered { component_id } => {
139 CapabilityCheckError::NotRegistered { component_id }
140 }
141 CapabilityMutationError::UndeclaredNeed { .. }
142 | CapabilityMutationError::AlreadyGranted { .. }
143 | CapabilityMutationError::SynchronizationPoisoned => {
144 CapabilityCheckError::SynchronizationPoisoned
145 }
146 })?;
147 checker.check(capability)
148 }
149
150 pub fn register(&self, meta: ComponentMeta, bytecode: Vec<u8>) -> Result<(), RegistryError> {
157 let mut records = self
158 .records
159 .lock()
160 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
161 validate_registration(&records, &meta, self.config.max_fragment_bytes)?;
162 let id = meta.id;
163 let needs = meta.needs.clone();
164 let record = Arc::new(ComponentRecord::new(meta, bytecode));
165 records.insert(id, record);
166 if self.capabilities.register_component(id, needs).is_err() {
167 records.remove(&id);
168 return Err(RegistryError::SynchronizationPoisoned);
169 }
170 if self
171 .events
172 .publish_transition(id, None, LifecycleState::Registered)
173 .is_err()
174 {
175 self.capabilities
176 .remove_component(id)
177 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
178 records.remove(&id);
179 return Err(RegistryError::SynchronizationPoisoned);
180 }
181 Ok(())
182 }
183
184 pub fn start(&self, id: ComponentId) -> Result<(), RegistryError> {
193 let record = self.record(id)?;
194 claim_operation(&record, id, Operation::Start)?;
195 if let Err(error) = self.check_running_dependencies(&record.meta) {
196 clear_operation(&record)?;
197 return Err(error);
198 }
199 transition(&record, &self.events, id, LifecycleState::Starting, None)?;
200 let load_result = {
201 let bytecode = record
202 .bytecode
203 .lock()
204 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
205 self.scheduler.hot_load_module(&bytecode)
206 };
207 let loaded = match load_result {
208 Ok(loaded) => loaded,
209 Err(error) => {
210 let reason = FailureReason {
211 child: None,
212 tombstone: None,
213 detail: error.to_string(),
214 };
215 transition(
216 &record,
217 &self.events,
218 id,
219 LifecycleState::Failed,
220 Some(reason),
221 )?;
222 clear_operation(&record)?;
223 return Err(RegistryError::ModuleLoad {
224 id,
225 detail: error.to_string(),
226 });
227 }
228 };
229 record
230 .modules
231 .lock()
232 .map_err(|_| RegistryError::SynchronizationPoisoned)?
233 .push(loaded.module_name);
234 if let Err(error) = self.refuse_deferred_bif_imports(id, loaded.module_name) {
235 transition(
236 &record,
237 &self.events,
238 id,
239 LifecycleState::Failed,
240 Some(failure_from_error(&error)),
241 )?;
242 clear_operation(&record)?;
243 return Err(error);
244 }
245 let policy = record
246 .meta
247 .supervision
248 .clone()
249 .ok_or(RegistryError::UndeclaredSupervision { id })?;
250 match start_tree(
251 id,
252 Arc::clone(&self.scheduler),
253 &record.meta.children,
254 policy,
255 self.config,
256 &record.status,
257 Arc::clone(&self.events),
258 ) {
259 Ok(tree) => {
260 *record
261 .tree
262 .lock()
263 .map_err(|_| RegistryError::SynchronizationPoisoned)? = Some(tree);
264 self.prepare_running_incarnation(id, &record)?;
265 transition(&record, &self.events, id, LifecycleState::Running, None)?;
266 clear_operation(&record)
267 }
268 Err(error) => {
269 let reason = failure_from_error(&error);
270 transition(
271 &record,
272 &self.events,
273 id,
274 LifecycleState::Failed,
275 Some(reason),
276 )?;
277 clear_operation(&record)?;
278 Err(error)
279 }
280 }
281 }
282
283 pub fn stop(&self, id: ComponentId) -> Result<StopOutcome, RegistryError> {
290 let record = self.record(id)?;
291 claim_operation(&record, id, Operation::Stop)?;
292 let current = state(&record)?;
293 if matches!(
294 current,
295 LifecycleState::Registered | LifecycleState::Stopped | LifecycleState::Failed
296 ) {
297 clear_operation(&record)?;
298 return Ok(StopOutcome::AlreadyStopped);
299 }
300 transition(&record, &self.events, id, LifecycleState::Stopping, None)?;
301 let tree = record
302 .tree
303 .lock()
304 .map_err(|_| RegistryError::SynchronizationPoisoned)?
305 .take()
306 .ok_or(RegistryError::MonitorTerminated { id })?;
307 match tree.stop(id) {
308 Ok(()) => {
309 clear_runtime(&record)?;
310 transition(&record, &self.events, id, LifecycleState::Stopped, None)?;
311 clear_operation(&record)?;
312 Ok(StopOutcome::Stopped)
313 }
314 Err(error) => {
315 transition(
316 &record,
317 &self.events,
318 id,
319 LifecycleState::Failed,
320 Some(failure_from_error(&error)),
321 )?;
322 clear_operation(&record)?;
323 Err(error)
324 }
325 }
326 }
327
328 pub fn remove(&self, id: ComponentId) -> Result<RemoveOutcome, RegistryError> {
335 let Some(record) = self.optional_record(id)? else {
336 return Ok(RemoveOutcome::AlreadyRemoved);
337 };
338 claim_operation(&record, id, Operation::Remove)?;
339 if let Err(error) = self.check_running_dependents(id) {
340 clear_operation(&record)?;
341 return Err(error);
342 }
343 let current = state(&record)?;
344 let mut forced_cleanup = None;
345 if current == LifecycleState::Running {
346 transition(&record, &self.events, id, LifecycleState::Stopping, None)?;
347 let tree = record
348 .tree
349 .lock()
350 .map_err(|_| RegistryError::SynchronizationPoisoned)?
351 .take()
352 .ok_or(RegistryError::MonitorTerminated { id })?;
353 match tree.stop(id) {
354 Ok(()) => {
355 clear_runtime(&record)?;
356 transition(&record, &self.events, id, LifecycleState::Stopped, None)?;
357 }
358 Err(error) => {
359 transition(
360 &record,
361 &self.events,
362 id,
363 LifecycleState::Failed,
364 Some(failure_from_error(&error)),
365 )?;
366 clear_operation(&record)?;
367 return Err(error);
368 }
369 }
370 } else if current == LifecycleState::Failed {
371 forced_cleanup = self.recover_failed_tree(id, &record)?;
372 }
373 self.unload_modules(id, &record)?;
374 let from = state(&record)?;
375 self.capabilities
376 .remove_component(id)
377 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
378 self.events
379 .publish_transition(id, Some(from), LifecycleState::Removed)
380 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
381 self.records
382 .lock()
383 .map_err(|_| RegistryError::SynchronizationPoisoned)?
384 .remove(&id);
385 Ok(forced_cleanup.map_or(
386 RemoveOutcome::Removed,
387 RemoveOutcome::RemovedAfterForcedCleanup,
388 ))
389 }
390
391 pub fn upgrade_bytecode(
413 &self,
414 id: ComponentId,
415 bytecode: Vec<u8>,
416 ) -> Result<(), RegistryError> {
417 let record = self.record(id)?;
418 claim_operation(&record, id, Operation::Upgrade)?;
419 let current = state(&record)?;
420 if !matches!(
421 current,
422 LifecycleState::Registered | LifecycleState::Stopped
423 ) {
424 clear_operation(&record)?;
425 return Err(RegistryError::UpgradeRequiresStopped { id, state: current });
426 }
427 if let Err(error) = self.purge_retained_old(id, &record) {
428 clear_operation(&record)?;
429 return Err(error);
430 }
431 *record
432 .bytecode
433 .lock()
434 .map_err(|_| RegistryError::SynchronizationPoisoned)? = bytecode;
435 clear_operation(&record)
436 }
437
438 fn purge_retained_old(
444 &self,
445 id: ComponentId,
446 record: &ComponentRecord,
447 ) -> Result<(), RegistryError> {
448 let modules = record
449 .modules
450 .lock()
451 .map_err(|_| RegistryError::SynchronizationPoisoned)?
452 .clone();
453 for module in unique_modules(modules) {
454 if self.scheduler.check_old_code(module) {
455 self.scheduler.purge_module(module).map_err(|error| {
456 RegistryError::ModulePurge {
457 id,
458 module: self.module_name(module),
459 detail: error.to_string(),
460 }
461 })?;
462 }
463 }
464 Ok(())
465 }
466
467 pub fn component_incarnation(&self, id: ComponentId) -> Result<u64, RegistryError> {
475 let record = self.record(id)?;
476 require_running(&record, id)?;
477 Ok(record
478 .incarnation
479 .load(std::sync::atomic::Ordering::Acquire))
480 }
481
482 pub fn component_module_generation(&self, id: ComponentId) -> Result<u64, RegistryError> {
491 let record = self.record(id)?;
492 let module = record
493 .modules
494 .lock()
495 .map_err(|_| RegistryError::SynchronizationPoisoned)?
496 .last()
497 .copied()
498 .ok_or_else(|| RegistryError::ModuleLoad {
499 id,
500 detail: "component has never loaded a module".to_owned(),
501 })?;
502 self.scheduler
503 .lookup_module_in(NamespaceId::DEFAULT, module)
504 .map(|loaded| loaded.generation())
505 .ok_or_else(|| RegistryError::ModuleLoad {
506 id,
507 detail: format!("module {} is absent from the VM", self.module_name(module)),
508 })
509 }
510
511 pub fn send_child(
517 &self,
518 id: ComponentId,
519 child: impl Into<String>,
520 message: i64,
521 ) -> Result<(), RegistryError> {
522 let record = self.record(id)?;
523 require_running(&record, id)?;
524 record
525 .tree
526 .lock()
527 .map_err(|_| RegistryError::SynchronizationPoisoned)?
528 .as_ref()
529 .ok_or(RegistryError::MonitorTerminated { id })?
530 .send(id, child.into(), message)
531 }
532
533 pub fn probe_child(
540 &self,
541 id: ComponentId,
542 child: impl Into<String>,
543 ) -> Result<i64, RegistryError> {
544 let record = self.record(id)?;
545 require_running(&record, id)?;
546 record
547 .tree
548 .lock()
549 .map_err(|_| RegistryError::SynchronizationPoisoned)?
550 .as_ref()
551 .ok_or(RegistryError::MonitorTerminated { id })?
552 .probe(id, child.into())
553 }
554
555 fn record(&self, id: ComponentId) -> Result<Arc<ComponentRecord>, RegistryError> {
556 self.optional_record(id)?
557 .ok_or(RegistryError::NotRegistered { id })
558 }
559
560 fn optional_record(
561 &self,
562 id: ComponentId,
563 ) -> Result<Option<Arc<ComponentRecord>>, RegistryError> {
564 self.records
565 .lock()
566 .map(|records| records.get(&id).cloned())
567 .map_err(|_| RegistryError::SynchronizationPoisoned)
568 }
569
570 fn check_running_dependencies(&self, meta: &ComponentMeta) -> Result<(), RegistryError> {
571 for required in &meta.requires {
572 let record = self.record(*required)?;
573 let required_state = state(&record)?;
574 if required_state != LifecycleState::Running {
575 return Err(RegistryError::DependencyNotRunning {
576 component: meta.id,
577 required: *required,
578 state: required_state,
579 });
580 }
581 }
582 Ok(())
583 }
584
585 fn check_running_dependents(&self, id: ComponentId) -> Result<(), RegistryError> {
586 let records = self
587 .records
588 .lock()
589 .map_err(|_| RegistryError::SynchronizationPoisoned)?;
590 for (dependent, record) in records.iter() {
591 if record.meta.requires.contains(&id) && state(record)? == LifecycleState::Running {
592 return Err(RegistryError::RunningDependent {
593 dependent: *dependent,
594 required: id,
595 });
596 }
597 }
598 Ok(())
599 }
600
601 fn unload_modules(
602 &self,
603 id: ComponentId,
604 record: &ComponentRecord,
605 ) -> Result<(), RegistryError> {
606 let modules = std::mem::take(
607 &mut *record
608 .modules
609 .lock()
610 .map_err(|_| RegistryError::SynchronizationPoisoned)?,
611 );
612 for module in unique_modules(modules) {
613 let name = self.module_name(module);
614 if self.scheduler.check_old_code(module) {
615 self.scheduler.purge_module(module).map_err(|error| {
616 RegistryError::ModulePurge {
617 id,
618 module: name.clone(),
619 detail: error.to_string(),
620 }
621 })?;
622 }
623 if !self.scheduler.delete_module(module) {
624 return Err(RegistryError::ModuleDelete { id, module: name });
625 }
626 if self
627 .scheduler
628 .lookup_module_in(NamespaceId::DEFAULT, module)
629 .is_some()
630 {
631 return Err(RegistryError::ModuleStillLoaded { id, module: name });
632 }
633 }
634 Ok(())
635 }
636
637 fn module_name(&self, module: Atom) -> String {
638 crate::composition::resolve_atom(&self.scheduler, module)
639 }
640
641 fn refuse_deferred_bif_imports(
647 &self,
648 id: ComponentId,
649 module: Atom,
650 ) -> Result<(), RegistryError> {
651 let Some(deferred) = crate::composition::deferred_erlang_imports(&self.scheduler, module)
652 else {
653 return Err(RegistryError::ModuleLoad {
654 id,
655 detail: "committed module was absent immediately after hot load".to_owned(),
656 });
657 };
658 if deferred.is_empty() {
659 return Ok(());
660 }
661 Err(RegistryError::DeferredBifImports {
662 id,
663 module: self.module_name(module),
664 imports: deferred.join(", "),
665 })
666 }
667}
668
669mod fragments;
670mod query;
671mod recovery;
672mod support;
673
674pub use recovery::{ChildCleanup, ForcedCleanupReport, ProcessCleanup};
675use support::{
676 ComponentRecord, Operation, claim_operation, clear_operation, clear_runtime,
677 failure_from_error, require_running, state, transition, unique_modules, validate_registration,
678};