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 = 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 let managed = self
270 .routes
271 .get_mut(route_id)
272 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
274 managed.pipeline_handle = Some(pipeline_handle);
275 managed.channel_sender = Some(tx_for_storage);
276
277 info!(route_id = %route_id, "Route started");
278 self.health_registry().mark_route_started(route_id);
279 Ok(())
280 }
281
282 async fn stop_route(&mut self, route_id: &str) -> Result<(), CamelError> {
283 self.stop_route_internal(route_id).await?;
284 self.health_registry().mark_route_stopped(route_id);
285 Ok(())
286 }
287
288 async fn restart_route(&mut self, route_id: &str) -> Result<(), CamelError> {
289 self.stop_route(route_id).await?;
290 tokio::time::sleep(Duration::from_millis(100)).await;
291 self.start_route(route_id).await
292 }
293
294 async fn suspend_route(&mut self, route_id: &str) -> Result<(), CamelError> {
295 let managed = self
297 .routes
298 .get_mut(route_id)
299 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
300
301 let consumer_running = handle_is_running(&managed.consumer_handle);
302 let pipeline_running = handle_is_running(&managed.pipeline_handle);
303
304 if !consumer_running || !pipeline_running {
306 return Err(CamelError::RouteError(format!(
307 "Cannot suspend route '{}' with execution lifecycle {}",
308 route_id,
309 inferred_lifecycle_label(managed)
310 )));
311 }
312
313 info!(route_id = %route_id, "Suspending route (consumer only, keeping pipeline)");
314
315 let managed = self
317 .routes
318 .get_mut(route_id)
319 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token.cancel();
321
322 let managed = self
324 .routes
325 .get_mut(route_id)
326 .expect("invariant: route must exist after prior existence check"); let consumer_handle = managed.consumer_handle.take();
328
329 let timeout_result = tokio::time::timeout(DEFAULT_SHUTDOWN_TIMEOUT, async {
331 if let Some(handle) = consumer_handle {
332 let _ = handle.await;
333 }
334 })
335 .await;
336
337 if timeout_result.is_err() {
338 warn!(route_id = %route_id, "Consumer shutdown timed out during suspend");
339 }
340
341 let managed = self
343 .routes
344 .get_mut(route_id)
345 .expect("invariant: route must exist after prior existence check"); managed.consumer_cancel_token = CancellationToken::new();
349
350 info!(route_id = %route_id, "Route suspended (pipeline still running)");
351 self.health_registry().mark_route_stopped(route_id);
352 Ok(())
353 }
354
355 async fn resume_route(&mut self, route_id: &str) -> Result<(), CamelError> {
356 let managed = self
358 .routes
359 .get(route_id)
360 .ok_or_else(|| CamelError::RouteError(format!("Route '{}' not found", route_id)))?;
361
362 let consumer_running = handle_is_running(&managed.consumer_handle);
363 let pipeline_running = handle_is_running(&managed.pipeline_handle);
364 if consumer_running || !pipeline_running {
365 return Err(CamelError::RouteError(format!(
366 "Cannot resume route '{}' with execution lifecycle {} (expected Suspended)",
367 route_id,
368 inferred_lifecycle_label(managed)
369 )));
370 }
371
372 let sender = managed.channel_sender.clone().ok_or_else(|| {
374 CamelError::RouteError("Suspended route has no channel sender".into())
375 })?;
376
377 let from_uri = managed.from_uri.clone();
379
380 info!(route_id = %route_id, "Resuming route (spawning consumer only)");
381
382 let consumer_component_ctx = Arc::new(ControllerComponentContext::new(
383 Arc::clone(&self.registry),
384 Arc::clone(&self.languages),
385 self.tracer_metrics
386 .clone()
387 .unwrap_or_else(|| Arc::new(NoOpMetrics)),
388 Arc::clone(&self.platform_service),
389 self.health_registry(),
390 Some(route_id.to_string()),
391 ));
392 let consumer_rt: Arc<dyn camel_component_api::RuntimeObservability> =
393 Arc::clone(&consumer_component_ctx) as Arc<_>;
394 let (mut consumer, _) = consumer_management::create_route_consumer(
395 consumer_rt,
396 &self.registry,
397 &from_uri,
398 consumer_component_ctx.as_ref(),
399 )?;
400
401 let managed = self
403 .routes
404 .get(route_id)
405 .expect("invariant: route must exist after prior existence check"); if let (Some(sp_config), Some(authenticator)) = (
407 managed.compiled.security_policy.as_ref(),
408 managed.compiled.security_authenticator.as_ref(),
409 ) {
410 use camel_component_api::SecurityContext;
411 let sec_ctx =
412 SecurityContext::from_arc(Arc::clone(&sp_config.policy), Arc::clone(authenticator));
413 consumer.set_security_context(sec_ctx);
414 }
415
416 let managed = self
418 .routes
419 .get_mut(route_id)
420 .expect("invariant: route must exist after prior existence check"); let consumer_cancel = managed.consumer_cancel_token.child_token();
424
425 let crash_notifier = self.crash_notifier.clone();
426 let runtime_for_consumer = self.runtime.clone();
427
428 let consumer_ctx =
430 ConsumerContext::new(sender, consumer_cancel.clone(), route_id.to_string());
431
432 let consumer_handle = consumer_management::spawn_consumer_task(
434 route_id.to_string(),
435 consumer,
436 consumer_ctx,
437 crash_notifier,
438 runtime_for_consumer,
439 true,
440 );
441
442 let managed = self
444 .routes
445 .get_mut(route_id)
446 .expect("invariant: route must exist after prior existence check"); managed.consumer_handle = Some(consumer_handle);
448
449 info!(route_id = %route_id, "Route resumed");
450 self.health_registry().mark_route_started(route_id);
451 Ok(())
452 }
453
454 async fn start_all_routes(&mut self) -> Result<(), CamelError> {
455 let route_ids: Vec<String> = {
458 let pairs = self.routes.auto_startup_sorted();
459 pairs.into_iter().map(|(id, _)| id).collect()
460 };
461
462 info!("Starting {} auto-startup routes", route_ids.len());
463
464 let mut errors: Vec<String> = Vec::new();
466 for route_id in route_ids {
467 if let Err(e) = self.start_route(&route_id).await {
468 errors.push(format!("Route '{}': {}", route_id, e));
469 }
470 }
471
472 if !errors.is_empty() {
473 return Err(CamelError::RouteError(format!(
474 "Failed to start routes: {}",
475 errors.join(", ")
476 )));
477 }
478
479 info!("All auto-startup routes started");
480 Ok(())
481 }
482
483 async fn stop_all_routes(&mut self) -> Result<(), CamelError> {
484 let route_ids: Vec<String> = {
486 let pairs = self.routes.shutdown_sorted();
487 pairs.into_iter().map(|(id, _)| id).collect()
488 };
489
490 info!("Stopping {} routes", route_ids.len());
491
492 for route_id in route_ids {
493 let _ = self.stop_route(&route_id).await;
494 }
495
496 info!("All routes stopped");
497 Ok(())
498 }
499}