camel_core/lifecycle/adapters/
route_controller_trait.rs1use std::sync::Arc;
7use std::time::Duration;
8
9use tokio::sync::mpsc;
10use tokio_util::sync::CancellationToken;
11use tower::Service;
12use tracing::{error, info, warn};
13
14use camel_api::{CamelError, NoOpMetrics, StepLifecycle, StepShutdownReason};
15use camel_component_api::{ConcurrencyModel, ConsumerContext, consumer::ExchangeEnvelope};
16
17use crate::lifecycle::adapters::consumer_management;
18use crate::lifecycle::adapters::controller_component_context::ControllerComponentContext;
19use crate::lifecycle::adapters::route_compiler::CANCEL_TOKEN;
20use crate::lifecycle::adapters::route_controller::DefaultRouteController;
21#[cfg(test)]
22use crate::lifecycle::adapters::route_helpers::emit_start_route_event;
23use crate::lifecycle::adapters::route_helpers::{
24 DrainGuard, handle_is_running, inferred_lifecycle_label, ready_with_backoff,
25};
26use crate::lifecycle::adapters::route_registry::DEFAULT_SHUTDOWN_TIMEOUT;
27
28async fn rollback_started(route_id: &str, handles: &[Arc<dyn StepLifecycle>]) {
39 for handle in handles.iter().rev() {
40 if let Err(e) = handle.shutdown(StepShutdownReason::RouteStop).await {
41 warn!(
42 route_id = %route_id,
43 step = handle.name(),
44 error = %e,
45 "best-effort step shutdown during start rollback failed"
46 );
47 }
48 }
49}
50
51#[async_trait::async_trait]
52impl camel_api::RouteController for DefaultRouteController {
53 async fn start_route(&mut self, route_id: &str) -> Result<(), CamelError> {
54 {
56 let managed = self
57 .routes
58 .get_mut(route_id)
59 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
60
61 let consumer_running = handle_is_running(&managed.consumer_handle);
62 let pipeline_running = handle_is_running(&managed.pipeline_handle);
63 if consumer_running && pipeline_running {
64 return Ok(());
65 }
66 if !consumer_running && pipeline_running {
67 return Err(CamelError::RouteError(format!(
68 "Route '{}' is suspended; use resume_route() to resume, or stop_route() then start_route() for full restart",
69 route_id
70 )));
71 }
72 if consumer_running && !pipeline_running {
73 return Err(CamelError::RouteError(format!(
74 "Route '{}' has inconsistent execution state; stop_route() then retry start_route()",
75 route_id
76 )));
77 }
78 }
79
80 info!(route_id = %route_id, "Starting route");
81
82 let (from_uri, pipeline, concurrency) = {
84 let managed = self
85 .routes
86 .get(route_id)
87 .expect("invariant: route must exist after prior existence check"); (
89 managed.from_uri.clone(),
90 Arc::clone(&managed.pipeline),
91 managed.concurrency.clone(),
92 )
93 };
94
95 let lifecycle_handles: Vec<Arc<dyn StepLifecycle>> = pipeline.load().lifecycle.clone();
101 for (idx, handle) in lifecycle_handles.iter().enumerate() {
102 if let Err(start_err) = handle.start().await {
103 warn!(
104 route_id = %route_id,
105 step = handle.name(),
106 "step start failed; rolling back already-started steps"
107 );
108 rollback_started(route_id, &lifecycle_handles[0..idx]).await;
110 return Err(start_err);
111 }
112 }
113
114 let crash_notifier = self.crash_notifier.clone();
116 let runtime_for_consumer = self.runtime.clone();
117
118 let consumer_component_ctx = Arc::new(ControllerComponentContext::new(
119 Arc::clone(&self.registry),
120 Arc::clone(&self.languages),
121 self.tracer_metrics
122 .clone()
123 .unwrap_or_else(|| Arc::new(NoOpMetrics)),
124 Arc::clone(&self.platform_service),
125 self.health_registry(),
126 Some(route_id.to_string()),
127 ));
128 let consumer_rt: Arc<dyn camel_component_api::RuntimeObservability> =
129 Arc::clone(&consumer_component_ctx) as Arc<_>;
130 let (mut consumer, consumer_concurrency) = match consumer_management::create_route_consumer(
131 consumer_rt,
132 &self.registry,
133 &from_uri,
134 consumer_component_ctx.as_ref(),
135 ) {
136 Ok(v) => v,
137 Err(e) => {
141 rollback_started(route_id, &lifecycle_handles).await;
142 return Err(e);
143 }
144 };
145
146 let effective_concurrency = concurrency.unwrap_or(consumer_concurrency);
148
149 let managed = self
151 .routes
152 .get_mut(route_id)
153 .expect("invariant: route must exist after prior existence check"); if let (Some(sp_config), Some(authenticator)) = (
157 managed.compiled.security_policy.as_ref(),
158 managed.compiled.security_authenticator.as_ref(),
159 ) {
160 use camel_component_api::SecurityContext;
161 let sec_ctx =
162 SecurityContext::from_arc(Arc::clone(&sp_config.policy), Arc::clone(authenticator));
163 consumer.set_security_context(sec_ctx);
164 }
165
166 let (tx, mut rx) = mpsc::channel::<ExchangeEnvelope>(256);
168 let consumer_cancel = managed.consumer_cancel_token.child_token();
170 let pipeline_cancel = managed.pipeline_cancel_token.child_token();
171 let drain_in_flight = Arc::clone(&managed.drain_in_flight);
172 let tx_for_storage = tx.clone();
174 let consumer_ctx = ConsumerContext::new(tx, consumer_cancel.clone(), route_id.to_string());
175
176 let split_clone = managed.aggregate_split.clone();
178 if let Some(split) = split_clone {
179 let result = self
180 .start_aggregate_route(
181 route_id,
182 split,
183 consumer,
184 consumer_ctx,
185 rx,
186 crash_notifier,
187 runtime_for_consumer,
188 tx_for_storage,
189 pipeline_cancel,
190 drain_in_flight,
191 )
192 .await;
193 if result.is_err() {
196 if let Some(managed) = self.routes.get_mut(route_id) {
199 managed.consumer_cancel_token.cancel();
200 }
201 rollback_started(route_id, &lifecycle_handles).await;
202 }
203 return result;
204 }
205 let pipeline_cancel_for_cleanup = pipeline_cancel.clone();
211
212 let pipeline_handle = match effective_concurrency {
214 ConcurrencyModel::Concurrent { max } => {
215 let sem = max.map(|n| Arc::new(tokio::sync::Semaphore::new(n)));
216 tokio::spawn(async move {
217 loop {
218 let permit = match &sem {
221 Some(s) => {
222 let acquired = tokio::select! {
223 p = Arc::clone(s).acquire_owned() => p.expect("semaphore closed"), _ = pipeline_cancel.cancelled() => return,
225 };
226 Some(acquired)
227 }
228 None => None,
229 };
230
231 let envelope = tokio::select! {
232 envelope = rx.recv() => match envelope {
233 Some(e) => e,
234 None => return,
235 },
236 _ = pipeline_cancel.cancelled() => return,
237 };
238 let ExchangeEnvelope { exchange, reply_tx } = envelope;
239 let pipe_ref = Arc::clone(&pipeline);
240 let cancel = pipeline_cancel.clone();
241 let drain_clone = Arc::clone(&drain_in_flight);
242 tokio::spawn(async move {
243 let _permit = permit;
245 let _drain_guard = DrainGuard::new(drain_clone);
246
247 let mut pipe = pipe_ref.load().processor.clone_inner();
249
250 if let Err(e) = ready_with_backoff(&mut pipe, &cancel).await {
252 if let Some(tx) = reply_tx {
253 let _ = tx.send(Err(e));
254 }
255 return;
256 }
257
258 let result = CANCEL_TOKEN
261 .scope(cancel, async move { pipe.call(exchange).await })
262 .await;
263 if let Some(tx) = reply_tx {
264 let _ = tx.send(result);
265 } else if let Err(ref e) = result {
266 error!("Pipeline error: {e}");
268 }
269 });
270 }
271 })
272 }
273 _ => {
279 tokio::spawn(async move {
280 loop {
281 let envelope = tokio::select! {
283 envelope = rx.recv() => match envelope {
284 Some(e) => e,
285 None => return, },
287 _ = pipeline_cancel.cancelled() => {
288 return;
290 }
291 };
292 let ExchangeEnvelope { exchange, reply_tx } = envelope;
293
294 let mut pipeline = pipeline.load().processor.clone_inner();
296
297 if let Err(e) = ready_with_backoff(&mut pipeline, &pipeline_cancel).await {
298 if let Some(tx) = reply_tx {
299 let _ = tx.send(Err(e));
300 }
301 return;
302 }
303
304 let cancel = pipeline_cancel.clone();
310 let _drain_guard = DrainGuard::new(Arc::clone(&drain_in_flight));
311 let result = CANCEL_TOKEN
312 .scope(cancel, async move { pipeline.call(exchange).await })
313 .await;
314 if let Some(tx) = reply_tx {
315 let _ = tx.send(result);
316 } else if let Err(ref e) = result {
317 error!("Pipeline error: {e}");
319 }
320 }
321 })
322 }
323 };
324 #[cfg(test)]
325 emit_start_route_event("pipeline_spawned");
326
327 let (consumer_handle, startup_rx) = consumer_management::spawn_consumer_task(
330 route_id.to_string(),
331 consumer,
332 consumer_ctx,
333 crash_notifier,
334 runtime_for_consumer,
335 false,
336 );
337 #[cfg(test)]
338 emit_start_route_event("consumer_spawned");
339
340 match consumer_management::await_consumer_startup(startup_rx, "startup").await {
345 Ok(()) => {}
346 Err(e) => {
347 consumer_handle.abort();
354 pipeline_cancel_for_cleanup.cancel();
355 consumer_cancel.cancel();
358 rollback_started(route_id, &lifecycle_handles).await;
359 return Err(e);
360 }
361 }
362
363 let managed = self
365 .routes
366 .get_mut(route_id)
367 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
369 managed.pipeline_handle = Some(pipeline_handle);
370 managed.channel_sender = Some(tx_for_storage);
371
372 info!(route_id = %route_id, "Route started");
373 self.health_registry().mark_route_started(route_id);
374 Ok(())
375 }
376
377 async fn stop_route(&mut self, route_id: &str) -> Result<(), CamelError> {
378 self.stop_route_internal(route_id).await?;
379 self.health_registry().mark_route_stopped(route_id);
380 Ok(())
381 }
382
383 async fn restart_route(&mut self, route_id: &str) -> Result<(), CamelError> {
384 self.stop_route(route_id).await?;
385 tokio::time::sleep(Duration::from_millis(100)).await;
386 self.start_route(route_id).await
387 }
388
389 async fn suspend_route(&mut self, route_id: &str) -> Result<(), CamelError> {
390 let managed = self
392 .routes
393 .get_mut(route_id)
394 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
395
396 let consumer_running = handle_is_running(&managed.consumer_handle);
397 let pipeline_running = handle_is_running(&managed.pipeline_handle);
398
399 if !consumer_running || !pipeline_running {
401 return Err(CamelError::RouteError(format!(
402 "Cannot suspend route '{}' with execution lifecycle {}",
403 route_id,
404 inferred_lifecycle_label(managed)
405 )));
406 }
407
408 info!(route_id = %route_id, "Suspending route (consumer only, keeping pipeline)");
409
410 let managed = self
412 .routes
413 .get_mut(route_id)
414 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token.cancel();
416
417 let managed = self
419 .routes
420 .get_mut(route_id)
421 .expect("invariant: route must exist after prior existence check"); let consumer_handle = managed.consumer_handle.take();
423
424 let timeout_result = tokio::time::timeout(DEFAULT_SHUTDOWN_TIMEOUT, async {
426 if let Some(handle) = consumer_handle {
427 let _ = handle.await;
428 }
429 })
430 .await;
431
432 if timeout_result.is_err() {
433 warn!(route_id = %route_id, "Consumer shutdown timed out during suspend");
434 }
435
436 let managed = self
438 .routes
439 .get_mut(route_id)
440 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token = CancellationToken::new();
444
445 info!(route_id = %route_id, "Route suspended (pipeline still running)");
446 self.health_registry().mark_route_stopped(route_id);
447 Ok(())
448 }
449
450 async fn resume_route(&mut self, route_id: &str) -> Result<(), CamelError> {
451 let managed = self
453 .routes
454 .get(route_id)
455 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
456
457 let consumer_running = handle_is_running(&managed.consumer_handle);
458 let pipeline_running = handle_is_running(&managed.pipeline_handle);
459 if consumer_running || !pipeline_running {
460 return Err(CamelError::RouteError(format!(
461 "Cannot resume route '{}' with execution lifecycle {} (expected Suspended)",
462 route_id,
463 inferred_lifecycle_label(managed)
464 )));
465 }
466
467 let sender = managed.channel_sender.clone().ok_or_else(|| {
469 CamelError::RouteError("Suspended route has no channel sender".into())
470 })?;
471
472 let from_uri = managed.from_uri.clone();
474
475 info!(route_id = %route_id, "Resuming route (spawning consumer only)");
476
477 let consumer_component_ctx = Arc::new(ControllerComponentContext::new(
478 Arc::clone(&self.registry),
479 Arc::clone(&self.languages),
480 self.tracer_metrics
481 .clone()
482 .unwrap_or_else(|| Arc::new(NoOpMetrics)),
483 Arc::clone(&self.platform_service),
484 self.health_registry(),
485 Some(route_id.to_string()),
486 ));
487 let consumer_rt: Arc<dyn camel_component_api::RuntimeObservability> =
488 Arc::clone(&consumer_component_ctx) as Arc<_>;
489 let (mut consumer, _) = consumer_management::create_route_consumer(
490 consumer_rt,
491 &self.registry,
492 &from_uri,
493 consumer_component_ctx.as_ref(),
494 )?;
495
496 let managed = self
498 .routes
499 .get(route_id)
500 .expect("invariant: route must exist after prior existence check"); if let (Some(sp_config), Some(authenticator)) = (
502 managed.compiled.security_policy.as_ref(),
503 managed.compiled.security_authenticator.as_ref(),
504 ) {
505 use camel_component_api::SecurityContext;
506 let sec_ctx =
507 SecurityContext::from_arc(Arc::clone(&sp_config.policy), Arc::clone(authenticator));
508 consumer.set_security_context(sec_ctx);
509 }
510
511 let managed = self
513 .routes
514 .get_mut(route_id)
515 .expect("invariant: route must exist after prior existence check"); let consumer_cancel = managed.consumer_cancel_token.child_token();
519
520 let crash_notifier = self.crash_notifier.clone();
521 let runtime_for_consumer = self.runtime.clone();
522
523 let consumer_ctx =
525 ConsumerContext::new(sender, consumer_cancel.clone(), route_id.to_string());
526
527 let (consumer_handle, startup_rx) = consumer_management::spawn_consumer_task(
529 route_id.to_string(),
530 consumer,
531 consumer_ctx,
532 crash_notifier,
533 runtime_for_consumer,
534 true,
535 );
536
537 consumer_management::await_consumer_startup(startup_rx, "resume").await?;
540
541 let managed = self
543 .routes
544 .get_mut(route_id)
545 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
547
548 info!(route_id = %route_id, "Route resumed");
549 self.health_registry().mark_route_started(route_id);
550 Ok(())
551 }
552
553 async fn start_all_routes(&mut self) -> Result<(), CamelError> {
554 let route_ids: Vec<String> = {
557 let pairs = self.routes.auto_startup_sorted();
558 pairs.into_iter().map(|(id, _)| id).collect()
559 };
560
561 info!("Starting {} auto-startup routes", route_ids.len());
562
563 let mut errors: Vec<String> = Vec::new();
565 for route_id in route_ids {
566 if let Err(e) = self.start_route(&route_id).await {
567 errors.push(format!("Route '{}': {}", route_id, e));
568 }
569 }
570
571 if !errors.is_empty() {
572 return Err(CamelError::RouteError(format!(
573 "Failed to start routes: {}",
574 errors.join(", ")
575 )));
576 }
577
578 info!("All auto-startup routes started");
579 Ok(())
580 }
581
582 async fn stop_all_routes(&mut self) -> Result<(), CamelError> {
583 let route_ids: Vec<String> = {
585 let pairs = self.routes.shutdown_sorted();
586 pairs.into_iter().map(|(id, _)| id).collect()
587 };
588
589 info!("Stopping {} routes", route_ids.len());
590
591 for route_id in route_ids {
592 let _ = self.stop_route(&route_id).await;
593 }
594
595 info!("All routes stopped");
596 Ok(())
597 }
598}