use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use camel_api::{CamelError, Lifecycle, RuntimeCommandBus};
use tokio::task::JoinHandle;
use tokio::time::timeout;
use tokio_util::sync::CancellationToken;
use tracing::{info, warn};
use crate::lifecycle::application::ports::{RouteDestructiveTeardownPort, RouteOrderingPort};
use crate::lifecycle::application::runtime_bus::RuntimeBus;
use crate::startup_validation::{ConfigCheck, run_startup_validation};
static CONTEXT_COMMAND_SEQ: AtomicU64 = AtomicU64::new(0);
pub(crate) fn next_context_command_id(op: &str, route_id: &str) -> String {
let seq = CONTEXT_COMMAND_SEQ.fetch_add(1, Ordering::Relaxed);
format!("context:{op}:{route_id}:{seq}")
}
pub(crate) async fn start_context(
services: &mut [Box<dyn Lifecycle>],
startup_checks: &mut Vec<Box<dyn ConfigCheck>>,
runtime: &RuntimeBus,
route_controller: &dyn RouteOrderingPort,
cancel_token: &mut CancellationToken,
) -> Result<(), CamelError> {
info!("Starting CamelContext");
*cancel_token = CancellationToken::new();
for (i, service) in services.iter_mut().enumerate() {
info!("Starting service: {}", service.name());
if let Err(e) = service.start().await {
warn!(
"Service {} failed to start, rolling back {} services",
service.name(),
i
);
for j in (0..i).rev() {
if let Err(rollback_err) = services[j].stop().await {
warn!(
"Failed to stop service {} during rollback: {}",
services[j].name(),
rollback_err
);
}
}
return Err(e);
}
}
let checks = std::mem::take(startup_checks);
if let Err(e) = run_startup_validation(checks) {
warn!("Startup validation failed: {e}");
return Err(e);
}
runtime
.reconcile_transient_states()
.await
.map_err(|e| CamelError::RouteError(format!("boot reconciliation failed: {e}")))?;
let route_ids = route_controller.auto_startup_route_ids().await?;
for route_id in route_ids {
runtime
.execute(camel_api::RuntimeCommand::StartRoute {
route_id: route_id.clone(),
command_id: next_context_command_id("start", &route_id),
causation_id: None,
})
.await?;
}
info!("CamelContext started");
Ok(())
}
pub(crate) async fn stop_context(
cancel_token: &CancellationToken,
supervision_join: &mut Option<JoinHandle<()>>,
runtime: &RuntimeBus,
route_controller: &dyn RouteOrderingPort,
services: &mut [Box<dyn Lifecycle>],
) -> Result<(), CamelError> {
info!("Stopping CamelContext");
cancel_token.cancel();
if let Some(join) = supervision_join.take() {
join.abort();
}
let route_ids = route_controller.shutdown_route_ids().await?;
for route_id in route_ids {
if let Err(err) = runtime
.execute(camel_api::RuntimeCommand::StopRoute {
route_id: route_id.clone(),
command_id: next_context_command_id("stop", &route_id),
causation_id: None,
})
.await
{
warn!(route_id = %route_id, error = %err, "Runtime stop command failed during context shutdown");
}
}
let mut first_error = None;
for service in services.iter_mut().rev() {
info!("Stopping service: {}", service.name());
if let Err(e) = service.stop().await {
warn!("Service {} failed to stop: {}", service.name(), e);
if first_error.is_none() {
first_error = Some(e);
}
}
}
info!("CamelContext stopped");
if let Some(e) = first_error {
Err(e)
} else {
Ok(())
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn abort_context(
cancel_token: &CancellationToken,
supervision_join: &mut Option<JoinHandle<()>>,
runtime: &RuntimeBus,
route_ordering: &dyn RouteOrderingPort,
route_teardown: &dyn RouteDestructiveTeardownPort,
services: &mut [Box<dyn Lifecycle>],
health_cancel_token: CancellationToken,
actor_join: &mut Option<JoinHandle<()>>,
) {
cancel_token.cancel();
if let Some(join) = supervision_join.take() {
join.abort();
}
let route_ids = route_ordering
.shutdown_route_ids()
.await
.unwrap_or_default();
for route_id in route_ids {
let _ = runtime
.execute(camel_api::RuntimeCommand::StopRoute {
route_id: route_id.clone(),
command_id: next_context_command_id("abort-stop", &route_id),
causation_id: None,
})
.await;
}
for service in services.iter_mut().rev() {
let name = service.name().to_string();
match timeout(Duration::from_secs(5), service.stop()).await {
Ok(Ok(())) => info!("Aborted service: {}", name),
Ok(Err(e)) => warn!("Service {} failed to stop during abort: {}", name, e),
Err(_) => warn!("Service {} timed out during abort (5s)", name),
}
}
let _ = route_teardown.shutdown().await;
health_cancel_token.cancel();
if let Some(mut join) = actor_join.take() {
match tokio::time::timeout(Duration::from_secs(5), &mut join).await {
Ok(Ok(())) => {}
Ok(Err(e)) => warn!("Controller actor task error during abort: {e}"),
Err(_) => {
warn!("Controller actor did not stop within 5s during abort; force-aborting");
join.abort();
let _ = join.await;
}
}
}
}