use std::{sync::Arc, time::Duration};
use aion_package::{ExtractionLimits, Package};
use aion_store::EventStore;
use aion_store::visibility::VisibilityStore;
use crate::{
ActivityServing, EngineError, Registry, RuntimeHandle, SignalDeliveryConfig, SupervisionTree,
WorkflowCatalog,
activity::bridge::ActivityDispatcher,
runtime::{
ChildNifBridge, ChildNifBridgeParts, install_child_nif_bridge, install_nif_runtime_context,
install_query_bridge, install_signal_nif_bridge,
nif_determinism::{NifContextSource, install_nif_context_source},
},
signal::SignalResumeHandoff,
};
use super::builder::WorkflowPackageSource;
pub(super) fn reserved_search_attribute_schema(
mut schema: aion_core::SearchAttributeSchema,
) -> Result<Arc<aion_core::SearchAttributeSchema>, EngineError> {
schema
.register(
aion_core::WORKFLOW_KIND_ATTRIBUTE,
aion_core::SearchAttributeType::String,
)
.map_err(|error| EngineError::Runtime {
reason: format!("search attribute schema conflict: {error}"),
})?;
Ok(Arc::new(schema))
}
pub(super) fn assemble_workloop_runtime(
configured: Option<(Arc<dyn aion_store::workloop::WorkloopStore>, Duration)>,
parts: &ChildBridgeAssembly<'_>,
) -> Result<Option<super::api_workloop::WorkloopEngineRuntime>, EngineError> {
let Some((workloop_store, sweep_interval)) = configured else {
return Ok(None);
};
let sink = Arc::new(crate::workloop::EngineLoopEventSink::new(
Arc::clone(parts.registry),
Arc::clone(parts.store),
Arc::clone(parts.visibility_store),
));
let waker = Arc::new(crate::workloop::EngineWorkloopWaker::new(
Arc::clone(parts.store),
Arc::clone(parts.visibility_store),
Arc::clone(parts.catalog),
Arc::clone(parts.runtime),
Arc::clone(parts.supervision),
Arc::clone(parts.registry),
Arc::clone(parts.search_attribute_schema),
));
let service = Arc::new(
crate::workloop::WorkloopService::new(
Arc::clone(&workloop_store),
sink,
waker,
sweep_interval,
)
.map_err(|error| EngineError::Runtime {
reason: format!("workloop service refused: {error}"),
})?,
);
crate::runtime::nif_workloop::install_workloop_nif_bridge(
parts.nif_state,
Arc::new(crate::runtime::nif_workloop::WorkloopNifBridge::new(
crate::workloop::close::IterationCloseContext {
workloop_store: Arc::clone(&workloop_store),
service: Arc::clone(&service),
store: Arc::clone(parts.store),
visibility_store: Arc::clone(parts.visibility_store),
registry: Arc::clone(parts.registry),
},
tokio::runtime::Handle::current(),
)),
);
let (shutdown, shutdown_rx) = tokio::sync::watch::channel(false);
let task = tokio::spawn(Arc::clone(&service).run(shutdown_rx));
Ok(Some(super::api_workloop::WorkloopEngineRuntime {
service,
store: workloop_store,
shutdown,
task,
nif_state: Arc::clone(parts.nif_state),
}))
}
pub(super) fn install_engine_nif_seams(
nif_state: &Arc<crate::runtime::EngineNifState>,
registry: &Arc<Registry>,
store: &Arc<dyn EventStore>,
runtime: &Arc<RuntimeHandle>,
activity_dispatcher: Option<Arc<dyn ActivityDispatcher>>,
query_timeout: Option<Duration>,
) -> Arc<dyn crate::engine_seam::EngineHandle> {
install_nif_runtime_context(
nif_state,
Arc::clone(registry),
Arc::clone(runtime),
tokio::runtime::Handle::current(),
);
crate::runtime::nif_timer_bridge::install_timer_nif_bridge(
nif_state,
Arc::clone(registry),
Arc::clone(store),
tokio::runtime::Handle::current(),
runtime.signal_delivery(),
);
install_nif_context_source(
nif_state,
Arc::new(NifContextSource::new(
Arc::clone(registry),
tokio::runtime::Handle::current(),
Arc::clone(store),
runtime.signal_delivery(),
)),
);
let query_mailbox_engine = install_query_bridge(
nif_state,
Arc::clone(registry),
runtime,
tokio::runtime::Handle::current(),
query_timeout,
);
if let Some(dispatcher) = activity_dispatcher {
nif_state.set_activity_dispatcher(dispatcher);
}
query_mailbox_engine
}
pub(super) async fn assemble_startup_catalog(
runtime: &RuntimeHandle,
store: &dyn EventStore,
sources: Vec<WorkflowPackageSource>,
serving: ActivityServing,
) -> Result<Arc<WorkflowCatalog>, EngineError> {
let catalog = Arc::new(WorkflowCatalog::new_with_serving(serving));
crate::loader::persistence::reload_persisted_packages(runtime, catalog.as_ref(), store).await?;
for source in sources {
let package = package_from_source(source)?;
let outcome = catalog.load_package(runtime, &package).await?;
tracing::info!(
workflow_type = outcome.record.workflow_type(),
content_hash = %outcome.record.version(),
freshly_loaded = outcome.freshly_loaded,
"loaded workflow package {}",
outcome.record.workflow_type()
);
}
Ok(catalog)
}
fn spawn_visibility_reconciliation_task(
interval: Option<Duration>,
store: Arc<dyn EventStore>,
visibility_store: Arc<dyn VisibilityStore>,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
match crate::lifecycle::visibility::reconcile_visibility(
Arc::clone(&store),
Arc::clone(&visibility_store),
)
.await
{
Ok(()) => tracing::info!("visibility reconciled against history at boot"),
Err(error) => tracing::warn!(
error = %error,
"boot visibility reconciliation failed; rows an earlier build projected wrongly \
stay as they are until a later reconciliation repairs them"
),
}
let Some(interval) = interval else {
return;
};
loop {
tokio::time::sleep(interval).await;
if let Err(error) = crate::lifecycle::visibility::reconcile_visibility(
Arc::clone(&store),
Arc::clone(&visibility_store),
)
.await
{
tracing::warn!(
error = %error,
"periodic visibility reconciliation failed; crash-consistency window may remain until a later reconciliation repairs visibility"
);
}
}
})
}
pub(super) fn claim_owned_shards(
store: &dyn EventStore,
owned_shards: Option<&[usize]>,
) -> Result<(), EngineError> {
apply_owned_shards(store, owned_shards);
acquire_owned_shards(store, owned_shards)
}
fn apply_owned_shards(store: &dyn EventStore, owned_shards: Option<&[usize]>) {
if let Some(shards) = owned_shards {
store.set_owned_shards(Some(shards));
}
}
fn acquire_owned_shards(
store: &dyn EventStore,
owned_shards: Option<&[usize]>,
) -> Result<(), EngineError> {
if let Some(shards) = owned_shards {
store.acquire_owned_shards(shards)?;
}
Ok(())
}
pub(super) fn spawn_visibility_reconciliation(
interval: Option<Duration>,
store: &Arc<dyn EventStore>,
visibility_store: &Arc<dyn VisibilityStore>,
) -> tokio::task::JoinHandle<()> {
spawn_visibility_reconciliation_task(interval, Arc::clone(store), Arc::clone(visibility_store))
}
pub(super) struct ChildBridgeAssembly<'a> {
pub(super) nif_state: &'a Arc<crate::runtime::EngineNifState>,
pub(super) store: &'a Arc<dyn EventStore>,
pub(super) visibility_store: &'a Arc<dyn VisibilityStore>,
pub(super) runtime: &'a Arc<RuntimeHandle>,
pub(super) catalog: &'a Arc<WorkflowCatalog>,
pub(super) registry: &'a Arc<Registry>,
pub(super) supervision: &'a Arc<SupervisionTree>,
pub(super) signal_handoff: &'a Arc<SignalResumeHandoff>,
pub(super) search_attribute_schema: &'a Arc<aion_core::SearchAttributeSchema>,
pub(super) watch_backoff: SignalDeliveryConfig,
}
fn register_workflow_deadline_handler(
nif_state: &crate::runtime::EngineNifState,
runtime: &Arc<RuntimeHandle>,
store: &Arc<dyn EventStore>,
visibility_store: &Arc<dyn VisibilityStore>,
registry: &Arc<Registry>,
) -> Result<(), EngineError> {
crate::runtime::nif_timer_bridge::register_deadline_handler(nif_state, |stand_down| {
Arc::new(crate::lifecycle::deadline::WorkflowDeadlineHandler::new(
Arc::downgrade(runtime),
Arc::clone(store),
Arc::clone(visibility_store),
Arc::clone(registry),
stand_down,
))
})
.map_err(|error| EngineError::Runtime {
reason: format!("failed to register workflow deadline handler: {error}"),
})
}
pub(super) fn install_workflow_nif_bridges(
assembly: &ChildBridgeAssembly<'_>,
delegated: &super::delegated::DelegatedSeams,
) -> Result<(), EngineError> {
install_signal_nif_bridge(
assembly.nif_state,
Arc::new(crate::runtime::SignalNifBridge::new(
Arc::clone(assembly.registry),
Arc::clone(assembly.runtime),
tokio::runtime::Handle::current(),
delegated.signal_router_arc(),
)),
);
install_configured_child_nif_bridge(assembly)
}
pub(super) fn install_configured_child_nif_bridge(
assembly: &ChildBridgeAssembly<'_>,
) -> Result<(), EngineError> {
install_child_nif_bridge(
assembly.nif_state,
Arc::new(ChildNifBridge::new(ChildNifBridgeParts {
store: Arc::clone(assembly.store),
visibility_store: Arc::clone(assembly.visibility_store),
runtime: Arc::clone(assembly.runtime),
catalog: Arc::clone(assembly.catalog),
registry: Arc::clone(assembly.registry),
supervision: Arc::clone(assembly.supervision),
signal_handoff: Arc::clone(assembly.signal_handoff),
search_attribute_schema: Arc::clone(assembly.search_attribute_schema),
tokio_handle: tokio::runtime::Handle::current(),
watch_backoff: assembly.watch_backoff,
})),
);
assembly
.nif_state
.set_workflow_catalog(Arc::clone(assembly.catalog));
register_workflow_deadline_handler(
assembly.nif_state,
assembly.runtime,
assembly.store,
assembly.visibility_store,
assembly.registry,
)
}
pub(super) fn package_from_source(source: WorkflowPackageSource) -> Result<Package, EngineError> {
match source {
WorkflowPackageSource::Path(path) => {
Package::load_from_path(&path, ExtractionLimits::unbounded()).map_err(|error| {
EngineError::Load {
reason: format!(
"failed to load workflow package `{}`: {error}",
path.display()
),
}
})
}
WorkflowPackageSource::Package(package) => Ok(*package),
}
}