frame-core 0.2.0

Component model, lifecycle, process isolation, and WASM module host
Documentation
use super::{
    Arc, Atom, ComponentId, ComponentMeta, ComponentStatus, EventHub, FailureReason, HashMap,
    HashSet, LifecycleState, Mutex, RegistryError, SharedStatus, StatusState, TreeHandle,
};

pub(super) struct ComponentRecord {
    pub(super) meta: ComponentMeta,
    pub(super) bytecode: 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>>,
}

impl ComponentRecord {
    pub(super) fn new(meta: ComponentMeta, bytecode: Vec<u8>) -> Self {
        Self {
            meta,
            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()),
        }
    }
}

#[derive(Clone, Copy)]
pub(super) enum Operation {
    Start,
    Stop,
    Remove,
}

pub(super) fn validate_registration(
    records: &HashMap<ComponentId, Arc<ComponentRecord>>,
    meta: &ComponentMeta,
) -> 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(),
            });
        }
    }
    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(())
}

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(&current).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 },
        });
    }
    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
}