use std::num::NonZeroUsize;
use std::sync::atomic::AtomicU64;
use crate::fragment::FragmentId;
use crate::status::ComponentStatus;
use super::{
Arc, Atom, ComponentId, ComponentMeta, EventHub, FailureReason, HashMap, HashSet,
LifecycleState, Mutex, RegistryError, SharedStatus, StatusState, TreeHandle,
};
pub(super) struct ComponentRecord {
pub(super) meta: ComponentMeta,
pub(super) bytecode: Mutex<Vec<u8>>,
pub(super) status: Arc<SharedStatus>,
pub(super) operation: Mutex<Option<Operation>>,
pub(super) tree: Mutex<Option<TreeHandle>>,
pub(super) modules: Mutex<Vec<Atom>>,
pub(super) incarnation: AtomicU64,
pub(super) fragment_content: Mutex<HashMap<FragmentId, Vec<u8>>>,
}
impl ComponentRecord {
pub(super) fn new(meta: ComponentMeta, bytecode: Vec<u8>) -> Self {
Self {
meta,
bytecode: Mutex::new(bytecode),
status: Arc::new(SharedStatus {
lifecycle: Mutex::new(StatusState {
state: LifecycleState::Registered,
failure: None,
}),
children: Mutex::new(Vec::new()),
supervisor: Mutex::new(None),
}),
operation: Mutex::new(None),
tree: Mutex::new(None),
modules: Mutex::new(Vec::new()),
incarnation: AtomicU64::new(0),
fragment_content: Mutex::new(HashMap::new()),
}
}
}
#[derive(Clone, Copy)]
pub(super) enum Operation {
Start,
Stop,
Remove,
Upgrade,
}
pub(super) fn validate_registration(
records: &HashMap<ComponentId, Arc<ComponentRecord>>,
meta: &ComponentMeta,
max_fragment_bytes: Option<NonZeroUsize>,
) -> Result<(), RegistryError> {
if records.contains_key(&meta.id) {
return Err(RegistryError::DuplicateComponent { id: meta.id });
}
let policy = meta
.supervision
.as_ref()
.ok_or(RegistryError::UndeclaredSupervision { id: meta.id })?;
if policy.window.is_zero() {
return Err(RegistryError::InvalidSupervisionWindow { id: meta.id });
}
let mut child_names = HashSet::new();
for child in &meta.children {
if !child_names.insert(child.name.clone()) {
return Err(RegistryError::DuplicateChild {
id: meta.id,
child: child.name.clone(),
});
}
}
for required in &meta.requires {
if !records.contains_key(required) {
return Err(RegistryError::MissingDependency {
component: meta.id,
required: *required,
});
}
if reaches(records, *required, meta.id, &mut HashSet::new()) {
return Err(RegistryError::CyclicDependency {
component: meta.id,
required: *required,
});
}
}
let mut needs = HashSet::new();
for request in &meta.needs {
request
.capability
.validate()
.map_err(|reason| RegistryError::InvalidCapabilityScope {
component: meta.id,
request: request.clone(),
reason,
})?;
if !needs.insert(request.clone()) {
return Err(RegistryError::DuplicateCapabilityNeed {
component: meta.id,
request: request.clone(),
});
}
}
validate_actions(meta)?;
validate_fragments(meta, max_fragment_bytes)?;
for capability in &meta.provides {
for (provider, record) in records {
if record
.meta
.provides
.iter()
.any(|existing| existing.id == capability.id)
{
return Err(RegistryError::CapabilityConflict {
capability: capability.id.clone(),
provider: *provider,
});
}
}
}
Ok(())
}
fn validate_actions(meta: &ComponentMeta) -> Result<(), RegistryError> {
let mut action_ids = HashSet::new();
for declaration in &meta.actions {
let action = declaration.id.as_str().to_owned();
if action.is_empty() {
return Err(RegistryError::InvalidActionDeclaration {
component: meta.id,
action,
detail: "action id is empty".to_owned(),
});
}
if declaration.description.is_empty() {
return Err(RegistryError::InvalidActionDeclaration {
component: meta.id,
action,
detail: "description is empty".to_owned(),
});
}
if declaration.request_schema.as_str().is_empty() {
return Err(RegistryError::InvalidActionDeclaration {
component: meta.id,
action,
detail: "request schema source is empty".to_owned(),
});
}
if declaration.response_schema.as_str().is_empty() {
return Err(RegistryError::InvalidActionDeclaration {
component: meta.id,
action,
detail: "response schema source is empty".to_owned(),
});
}
if !action_ids.insert(declaration.id.clone()) {
return Err(RegistryError::DuplicateActionId {
component: meta.id,
action,
});
}
let mut requirements = HashSet::new();
for requirement in &declaration.requirements {
if !requirements.insert(requirement.clone()) {
return Err(RegistryError::DuplicateActionRequirement {
component: meta.id,
action,
requirement: requirement.clone(),
});
}
if !meta
.needs
.iter()
.any(|need| need.capability.covers(requirement))
{
return Err(RegistryError::ActionRequirementUndeclared {
component: meta.id,
action,
requirement: requirement.clone(),
});
}
}
}
Ok(())
}
fn validate_fragments(
meta: &ComponentMeta,
max_fragment_bytes: Option<NonZeroUsize>,
) -> Result<(), RegistryError> {
if meta.fragments.is_empty() {
return Ok(());
}
let Some(limit) = max_fragment_bytes else {
return Err(RegistryError::FragmentLimitUnconfigured { component: meta.id });
};
let mut fragment_ids = HashSet::new();
for declaration in &meta.fragments {
let fragment = declaration.id.as_str().to_owned();
if fragment.is_empty() {
return Err(RegistryError::InvalidFragmentDeclaration {
component: meta.id,
fragment,
detail: "fragment id is empty".to_owned(),
});
}
if declaration.kind.as_str().is_empty() {
return Err(RegistryError::InvalidFragmentDeclaration {
component: meta.id,
fragment,
detail: "fragment kind is empty".to_owned(),
});
}
if declaration.content.len() > limit.get() {
return Err(RegistryError::FragmentContentOversize {
component: meta.id,
fragment,
size: declaration.content.len(),
limit: limit.get(),
});
}
if !fragment_ids.insert(declaration.id.clone()) {
return Err(RegistryError::DuplicateFragmentId {
component: meta.id,
fragment,
});
}
}
Ok(())
}
pub(super) fn reaches(
records: &HashMap<ComponentId, Arc<ComponentRecord>>,
current: ComponentId,
target: ComponentId,
visited: &mut HashSet<ComponentId>,
) -> bool {
if current == target {
return true;
}
visited.insert(current)
&& records.get(¤t).is_some_and(|record| {
record
.meta
.requires
.iter()
.any(|next| reaches(records, *next, target, visited))
})
}
pub(super) fn claim_operation(
record: &ComponentRecord,
id: ComponentId,
requested: Operation,
) -> Result<(), RegistryError> {
let mut operation = record
.operation
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?;
if let Some(active) = *operation {
return Err(match active {
Operation::Start => RegistryError::AlreadyStarting { id },
Operation::Stop => RegistryError::AlreadyStopping { id },
Operation::Remove => RegistryError::AlreadyRemoving { id },
Operation::Upgrade => RegistryError::AlreadyUpgrading { id },
});
}
let current = state(record)?;
if matches!(requested, Operation::Start) {
if current == LifecycleState::Running {
return Err(RegistryError::AlreadyRunning { id });
}
if current == LifecycleState::Starting {
return Err(RegistryError::AlreadyStarting { id });
}
}
*operation = Some(requested);
Ok(())
}
pub(super) fn clear_operation(record: &ComponentRecord) -> Result<(), RegistryError> {
*record
.operation
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)? = None;
Ok(())
}
pub(super) fn transition(
record: &ComponentRecord,
events: &EventHub,
id: ComponentId,
to: LifecycleState,
failure: Option<FailureReason>,
) -> Result<(), RegistryError> {
let from = {
let mut status = record
.status
.lifecycle
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?;
let from = status.state;
status.state = to;
status.failure = failure;
from
};
events
.publish_transition(id, Some(from), to)
.map_err(|_| RegistryError::SynchronizationPoisoned)
}
pub(super) fn state(record: &ComponentRecord) -> Result<LifecycleState, RegistryError> {
record
.status
.lifecycle
.lock()
.map(|status| status.state)
.map_err(|_| RegistryError::SynchronizationPoisoned)
}
pub(super) fn require_running(
record: &ComponentRecord,
id: ComponentId,
) -> Result<(), RegistryError> {
let current = state(record)?;
if current == LifecycleState::Running {
Ok(())
} else {
Err(RegistryError::DependencyNotRunning {
component: id,
required: id,
state: current,
})
}
}
pub(super) fn clear_runtime(record: &ComponentRecord) -> Result<(), RegistryError> {
record
.status
.children
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?
.clear();
*record
.status
.supervisor
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)? = None;
Ok(())
}
pub(super) fn snapshot(
id: ComponentId,
record: &ComponentRecord,
) -> Result<ComponentStatus, RegistryError> {
let status = record
.status
.lifecycle
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?;
let children = record
.status
.children
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?
.clone();
Ok(ComponentStatus {
id,
state: status.state,
children,
failure: status.failure.clone(),
})
}
pub(super) fn failure_from_error(error: &RegistryError) -> FailureReason {
match error {
RegistryError::StartFailed { reason, .. } | RegistryError::StopFailed { reason, .. } => {
reason.clone()
}
_ => FailureReason {
child: None,
tombstone: None,
detail: error.to_string(),
},
}
}
pub(super) fn unique_modules(modules: Vec<Atom>) -> Vec<Atom> {
let mut unique = Vec::new();
for module in modules {
if !unique.contains(&module) {
unique.push(module);
}
}
unique
}