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};
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
28#[async_trait::async_trait]
29impl camel_api::RouteController for DefaultRouteController {
30 async fn start_route(&mut self, route_id: &str) -> Result<(), CamelError> {
31 {
33 let managed = self
34 .routes
35 .get_mut(route_id)
36 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
37
38 let consumer_running = handle_is_running(&managed.consumer_handle);
39 let pipeline_running = handle_is_running(&managed.pipeline_handle);
40 if consumer_running && pipeline_running {
41 return Ok(());
42 }
43 if !consumer_running && pipeline_running {
44 return Err(CamelError::RouteError(format!(
45 "Route '{}' is suspended; use resume_route() to resume, or stop_route() then start_route() for full restart",
46 route_id
47 )));
48 }
49 if consumer_running && !pipeline_running {
50 return Err(CamelError::RouteError(format!(
51 "Route '{}' has inconsistent execution state; stop_route() then retry start_route()",
52 route_id
53 )));
54 }
55 }
56
57 info!(route_id = %route_id, "Starting route");
58
59 let (from_uri, pipeline, concurrency) = {
61 let managed = self
62 .routes
63 .get(route_id)
64 .expect("invariant: route must exist after prior existence check"); (
66 managed.from_uri.clone(),
67 Arc::clone(&managed.pipeline),
68 managed.concurrency.clone(),
69 )
70 };
71
72 let crash_notifier = self.crash_notifier.clone();
74 let runtime_for_consumer = self.runtime.clone();
75
76 let consumer_component_ctx = Arc::new(ControllerComponentContext::new(
77 Arc::clone(&self.registry),
78 Arc::clone(&self.languages),
79 self.tracer_metrics
80 .clone()
81 .unwrap_or_else(|| Arc::new(NoOpMetrics)),
82 Arc::clone(&self.platform_service),
83 self.health_registry(),
84 Some(route_id.to_string()),
85 ));
86 let consumer_rt: Arc<dyn camel_component_api::RuntimeObservability> =
87 Arc::clone(&consumer_component_ctx) as Arc<_>;
88 let (mut consumer, consumer_concurrency) = consumer_management::create_route_consumer(
89 consumer_rt,
90 &self.registry,
91 &from_uri,
92 consumer_component_ctx.as_ref(),
93 )?;
94
95 let effective_concurrency = concurrency.unwrap_or(consumer_concurrency);
97
98 let managed = self
100 .routes
101 .get_mut(route_id)
102 .expect("invariant: route must exist after prior existence check"); if let (Some(sp_config), Some(authenticator)) = (
106 managed.compiled.security_policy.as_ref(),
107 managed.compiled.security_authenticator.as_ref(),
108 ) {
109 use camel_component_api::SecurityContext;
110 let sec_ctx =
111 SecurityContext::from_arc(Arc::clone(&sp_config.policy), Arc::clone(authenticator));
112 consumer.set_security_context(sec_ctx);
113 }
114
115 let (tx, mut rx) = mpsc::channel::<ExchangeEnvelope>(256);
117 let consumer_cancel = managed.consumer_cancel_token.child_token();
119 let pipeline_cancel = managed.pipeline_cancel_token.child_token();
120 let drain_in_flight = Arc::clone(&managed.drain_in_flight);
121 let tx_for_storage = tx.clone();
123 let consumer_ctx = ConsumerContext::new(tx, consumer_cancel.clone(), route_id.to_string());
124
125 let split_clone = managed.aggregate_split.clone();
127 if let Some(split) = split_clone {
128 return self
129 .start_aggregate_route(
130 route_id,
131 split,
132 consumer,
133 consumer_ctx,
134 rx,
135 crash_notifier,
136 runtime_for_consumer,
137 tx_for_storage,
138 pipeline_cancel,
139 drain_in_flight,
140 )
141 .await;
142 }
143 let pipeline_handle = match effective_concurrency {
147 ConcurrencyModel::Sequential => {
148 tokio::spawn(async move {
149 loop {
150 let envelope = tokio::select! {
152 envelope = rx.recv() => match envelope {
153 Some(e) => e,
154 None => return, },
156 _ = pipeline_cancel.cancelled() => {
157 return;
159 }
160 };
161 let ExchangeEnvelope { exchange, reply_tx } = envelope;
162
163 let mut pipeline = pipeline.load().processor.clone_inner();
165
166 if let Err(e) = ready_with_backoff(&mut pipeline, &pipeline_cancel).await {
167 if let Some(tx) = reply_tx {
168 let _ = tx.send(Err(e));
169 }
170 return;
171 }
172
173 let cancel = pipeline_cancel.clone();
179 let _drain_guard = DrainGuard::new(Arc::clone(&drain_in_flight));
180 let result = CANCEL_TOKEN
181 .scope(cancel, async move { pipeline.call(exchange).await })
182 .await;
183 if let Some(tx) = reply_tx {
184 let _ = tx.send(result);
185 } else if let Err(ref e) = result {
186 error!("Pipeline error: {e}");
188 }
189 }
190 })
191 }
192 ConcurrencyModel::Concurrent { max } => {
193 let sem = max.map(|n| Arc::new(tokio::sync::Semaphore::new(n)));
194 tokio::spawn(async move {
195 loop {
196 let permit = match &sem {
199 Some(s) => {
200 let acquired = tokio::select! {
201 p = Arc::clone(s).acquire_owned() => p.expect("semaphore closed"), _ = pipeline_cancel.cancelled() => return,
203 };
204 Some(acquired)
205 }
206 None => None,
207 };
208
209 let envelope = tokio::select! {
210 envelope = rx.recv() => match envelope {
211 Some(e) => e,
212 None => return,
213 },
214 _ = pipeline_cancel.cancelled() => return,
215 };
216 let ExchangeEnvelope { exchange, reply_tx } = envelope;
217 let pipe_ref = Arc::clone(&pipeline);
218 let cancel = pipeline_cancel.clone();
219 let drain_clone = Arc::clone(&drain_in_flight);
220 tokio::spawn(async move {
221 let _permit = permit;
223 let _drain_guard = DrainGuard::new(drain_clone);
224
225 let mut pipe = pipe_ref.load().processor.clone_inner();
227
228 if let Err(e) = ready_with_backoff(&mut pipe, &cancel).await {
230 if let Some(tx) = reply_tx {
231 let _ = tx.send(Err(e));
232 }
233 return;
234 }
235
236 let result = CANCEL_TOKEN
239 .scope(cancel, async move { pipe.call(exchange).await })
240 .await;
241 if let Some(tx) = reply_tx {
242 let _ = tx.send(result);
243 } else if let Err(ref e) = result {
244 error!("Pipeline error: {e}");
246 }
247 });
248 }
249 })
250 }
251 };
252 #[cfg(test)]
253 emit_start_route_event("pipeline_spawned");
254
255 let (consumer_handle, startup_rx) = consumer_management::spawn_consumer_task(
258 route_id.to_string(),
259 consumer,
260 consumer_ctx,
261 crash_notifier,
262 runtime_for_consumer,
263 false,
264 );
265 #[cfg(test)]
266 emit_start_route_event("consumer_spawned");
267
268 consumer_management::await_consumer_startup(startup_rx, "startup").await?;
273
274 let managed = self
276 .routes
277 .get_mut(route_id)
278 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
280 managed.pipeline_handle = Some(pipeline_handle);
281 managed.channel_sender = Some(tx_for_storage);
282
283 info!(route_id = %route_id, "Route started");
284 self.health_registry().mark_route_started(route_id);
285 Ok(())
286 }
287
288 async fn stop_route(&mut self, route_id: &str) -> Result<(), CamelError> {
289 self.stop_route_internal(route_id).await?;
290 self.health_registry().mark_route_stopped(route_id);
291 Ok(())
292 }
293
294 async fn restart_route(&mut self, route_id: &str) -> Result<(), CamelError> {
295 self.stop_route(route_id).await?;
296 tokio::time::sleep(Duration::from_millis(100)).await;
297 self.start_route(route_id).await
298 }
299
300 async fn suspend_route(&mut self, route_id: &str) -> Result<(), CamelError> {
301 let managed = self
303 .routes
304 .get_mut(route_id)
305 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
306
307 let consumer_running = handle_is_running(&managed.consumer_handle);
308 let pipeline_running = handle_is_running(&managed.pipeline_handle);
309
310 if !consumer_running || !pipeline_running {
312 return Err(CamelError::RouteError(format!(
313 "Cannot suspend route '{}' with execution lifecycle {}",
314 route_id,
315 inferred_lifecycle_label(managed)
316 )));
317 }
318
319 info!(route_id = %route_id, "Suspending route (consumer only, keeping pipeline)");
320
321 let managed = self
323 .routes
324 .get_mut(route_id)
325 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token.cancel();
327
328 let managed = self
330 .routes
331 .get_mut(route_id)
332 .expect("invariant: route must exist after prior existence check"); let consumer_handle = managed.consumer_handle.take();
334
335 let timeout_result = tokio::time::timeout(DEFAULT_SHUTDOWN_TIMEOUT, async {
337 if let Some(handle) = consumer_handle {
338 let _ = handle.await;
339 }
340 })
341 .await;
342
343 if timeout_result.is_err() {
344 warn!(route_id = %route_id, "Consumer shutdown timed out during suspend");
345 }
346
347 let managed = self
349 .routes
350 .get_mut(route_id)
351 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token = CancellationToken::new();
355
356 info!(route_id = %route_id, "Route suspended (pipeline still running)");
357 self.health_registry().mark_route_stopped(route_id);
358 Ok(())
359 }
360
361 async fn resume_route(&mut self, route_id: &str) -> Result<(), CamelError> {
362 let managed = self
364 .routes
365 .get(route_id)
366 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
367
368 let consumer_running = handle_is_running(&managed.consumer_handle);
369 let pipeline_running = handle_is_running(&managed.pipeline_handle);
370 if consumer_running || !pipeline_running {
371 return Err(CamelError::RouteError(format!(
372 "Cannot resume route '{}' with execution lifecycle {} (expected Suspended)",
373 route_id,
374 inferred_lifecycle_label(managed)
375 )));
376 }
377
378 let sender = managed.channel_sender.clone().ok_or_else(|| {
380 CamelError::RouteError("Suspended route has no channel sender".into())
381 })?;
382
383 let from_uri = managed.from_uri.clone();
385
386 info!(route_id = %route_id, "Resuming route (spawning consumer only)");
387
388 let consumer_component_ctx = Arc::new(ControllerComponentContext::new(
389 Arc::clone(&self.registry),
390 Arc::clone(&self.languages),
391 self.tracer_metrics
392 .clone()
393 .unwrap_or_else(|| Arc::new(NoOpMetrics)),
394 Arc::clone(&self.platform_service),
395 self.health_registry(),
396 Some(route_id.to_string()),
397 ));
398 let consumer_rt: Arc<dyn camel_component_api::RuntimeObservability> =
399 Arc::clone(&consumer_component_ctx) as Arc<_>;
400 let (mut consumer, _) = consumer_management::create_route_consumer(
401 consumer_rt,
402 &self.registry,
403 &from_uri,
404 consumer_component_ctx.as_ref(),
405 )?;
406
407 let managed = self
409 .routes
410 .get(route_id)
411 .expect("invariant: route must exist after prior existence check"); if let (Some(sp_config), Some(authenticator)) = (
413 managed.compiled.security_policy.as_ref(),
414 managed.compiled.security_authenticator.as_ref(),
415 ) {
416 use camel_component_api::SecurityContext;
417 let sec_ctx =
418 SecurityContext::from_arc(Arc::clone(&sp_config.policy), Arc::clone(authenticator));
419 consumer.set_security_context(sec_ctx);
420 }
421
422 let managed = self
424 .routes
425 .get_mut(route_id)
426 .expect("invariant: route must exist after prior existence check"); let consumer_cancel = managed.consumer_cancel_token.child_token();
430
431 let crash_notifier = self.crash_notifier.clone();
432 let runtime_for_consumer = self.runtime.clone();
433
434 let consumer_ctx =
436 ConsumerContext::new(sender, consumer_cancel.clone(), route_id.to_string());
437
438 let (consumer_handle, startup_rx) = consumer_management::spawn_consumer_task(
440 route_id.to_string(),
441 consumer,
442 consumer_ctx,
443 crash_notifier,
444 runtime_for_consumer,
445 true,
446 );
447
448 consumer_management::await_consumer_startup(startup_rx, "resume").await?;
451
452 let managed = self
454 .routes
455 .get_mut(route_id)
456 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
458
459 info!(route_id = %route_id, "Route resumed");
460 self.health_registry().mark_route_started(route_id);
461 Ok(())
462 }
463
464 async fn start_all_routes(&mut self) -> Result<(), CamelError> {
465 let route_ids: Vec<String> = {
468 let pairs = self.routes.auto_startup_sorted();
469 pairs.into_iter().map(|(id, _)| id).collect()
470 };
471
472 info!("Starting {} auto-startup routes", route_ids.len());
473
474 let mut errors: Vec<String> = Vec::new();
476 for route_id in route_ids {
477 if let Err(e) = self.start_route(&route_id).await {
478 errors.push(format!("Route '{}': {}", route_id, e));
479 }
480 }
481
482 if !errors.is_empty() {
483 return Err(CamelError::RouteError(format!(
484 "Failed to start routes: {}",
485 errors.join(", ")
486 )));
487 }
488
489 info!("All auto-startup routes started");
490 Ok(())
491 }
492
493 async fn stop_all_routes(&mut self) -> Result<(), CamelError> {
494 let route_ids: Vec<String> = {
496 let pairs = self.routes.shutdown_sorted();
497 pairs.into_iter().map(|(id, _)| id).collect()
498 };
499
500 info!("Stopping {} routes", route_ids.len());
501
502 for route_id in route_ids {
503 let _ = self.stop_route(&route_id).await;
504 }
505
506 info!("All routes stopped");
507 Ok(())
508 }
509}