1use std::{
2 any::{Any, TypeId},
3 cell::RefCell,
4 collections::{BTreeMap, HashMap},
5 fmt,
6 panic::{AssertUnwindSafe, catch_unwind},
7 rc::Rc,
8 sync::{Arc, mpsc as std_mpsc},
9 thread,
10 time::{Duration, Instant},
11};
12
13use cpu_time::ThreadTime;
14use futures::{channel::oneshot, future::Either};
15use lenso_app_plan::{ExecutionLaneId, ResolvedAppPlan};
16use lenso_kernel::{
17 CancellationToken, EventCapability, ExecutionAdapterCatalog, NativeApp, NativeEventHandle,
18 NativeRequestHandle, NativeStream, NativeStreamHandle, RequestCapability, RuntimeDiagnostics,
19 RuntimeFailure, ShutdownOutcome, StreamCapability,
20};
21use tokio::sync::{mpsc, watch};
22
23use crate::TokioDriver;
24
25mod admission;
26mod diagnostics;
27mod error;
28mod interaction_transfer;
29mod projection;
30mod terminal;
31mod transfer;
32
33pub use diagnostics::LaneDiagnosticsSnapshot;
34use diagnostics::{LaneDiagnosticsState, LaneInvocationProbe};
35pub use error::ReplicatedRunnerError;
36use interaction_transfer::CrossLaneInteractionCatalog;
37use projection::{LaneProxyAdapter, project_lane};
38use terminal::ReplicatedTerminalState;
39pub use transfer::CrossLaneRequestCatalog;
40
41const LANE_PROXY_EXECUTION_CLASS: &str = "lenso.native-lane-proxy@1";
42
43#[derive(Clone, Debug, Default)]
45pub struct LaneInvocationOptions {
46 timeout: Option<Duration>,
47 cancellation: Option<LaneCancellationToken>,
48}
49
50impl LaneInvocationOptions {
51 pub const fn new() -> Self {
53 Self {
54 timeout: None,
55 cancellation: None,
56 }
57 }
58
59 #[must_use]
61 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
62 self.timeout = Some(timeout);
63 self
64 }
65
66 #[must_use]
68 pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
69 self.cancellation = Some(cancellation);
70 self
71 }
72}
73
74#[derive(Clone, Debug)]
76pub struct LaneCancellationToken {
77 cancelled: watch::Sender<bool>,
78}
79
80impl Default for LaneCancellationToken {
81 fn default() -> Self {
82 let (cancelled, _) = watch::channel(false);
83 Self { cancelled }
84 }
85}
86
87impl LaneCancellationToken {
88 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn cancel(&self) {
95 self.cancelled.send_replace(true);
96 }
97
98 pub fn is_cancelled(&self) -> bool {
100 *self.cancelled.borrow()
101 }
102
103 async fn cancelled(&self) {
104 let mut cancelled = self.cancelled.subscribe();
105 loop {
106 if *cancelled.borrow_and_update() {
107 return;
108 }
109 if cancelled.changed().await.is_err() {
110 return;
111 }
112 }
113 }
114}
115
116type LaneTask = Box<dyn FnOnce(LaneRuntime) + Send + 'static>;
117type LaneSender = mpsc::Sender<LaneTask>;
118type LaneRoute = mpsc::WeakSender<LaneTask>;
119type CrossLaneDiagnostics = (Arc<LaneDiagnosticsState>, ExecutionLaneId, Arc<str>);
120type RequestRouteIndex = BTreeMap<String, BTreeMap<String, PlannedRequestRoute>>;
121
122#[derive(Clone, Debug, Eq, PartialEq)]
123struct PlannedRequestRoute {
124 caller_instance: Arc<str>,
125 provider_instance: Arc<str>,
126 consumer_lane: ExecutionLaneId,
127 provider_lane: ExecutionLaneId,
128 providers: usize,
129}
130
131struct LaneShutdown {
132 timeout: Duration,
133 completed: oneshot::Sender<ShutdownOutcome>,
134}
135
136struct LaneHandle {
137 id: ExecutionLaneId,
138 commands: LaneSender,
139 shutdown: oneshot::Sender<LaneShutdown>,
140 thread: thread::JoinHandle<()>,
141}
142
143type TypedRequestHandles = HashMap<String, Box<dyn Any>>;
144type TypedStreamSessions = HashMap<(TypeId, u64), Box<dyn Any>>;
145
146#[derive(Clone)]
147struct LaneRuntime {
148 app: NativeApp,
149 request_handles: Rc<RefCell<HashMap<TypeId, TypedRequestHandles>>>,
150 stream_sessions: Rc<RefCell<TypedStreamSessions>>,
151}
152
153impl LaneRuntime {
154 fn new(app: NativeApp) -> Self {
155 Self {
156 app,
157 request_handles: Rc::new(RefCell::new(HashMap::new())),
158 stream_sessions: Rc::new(RefCell::new(HashMap::new())),
159 }
160 }
161
162 fn request_handle<C: RequestCapability>(
163 &self,
164 caller_instance: &str,
165 ) -> Result<Rc<NativeRequestHandle<C>>, RuntimeFailure> {
166 let capability = TypeId::of::<C>();
167 if let Some(handle) = self
168 .request_handles
169 .borrow()
170 .get(&capability)
171 .and_then(|handles| handles.get(caller_instance))
172 .and_then(|handle| handle.downcast_ref::<Rc<NativeRequestHandle<C>>>())
173 {
174 return Ok(handle.clone());
175 }
176 let handle = Rc::new(self.app.handle::<C>(caller_instance)?);
177 self.request_handles
178 .borrow_mut()
179 .entry(capability)
180 .or_default()
181 .insert(caller_instance.to_owned(), Box::new(handle.clone()));
182 Ok(handle)
183 }
184
185 fn stream_handle<C: StreamCapability>(
186 &self,
187 caller_instance: &str,
188 provider_instance: &str,
189 ) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
190 let dependencies = self.app.dependencies(caller_instance)?;
191 dependencies
192 .bindings()
193 .iter()
194 .find(|binding| {
195 binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
196 })
197 .and_then(lenso_kernel::PluginDependency::stream_handle)
198 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
199 .typed::<C>()
200 }
201
202 fn event_handle<C: EventCapability>(
203 &self,
204 caller_instance: &str,
205 provider_instance: &str,
206 ) -> Result<NativeEventHandle<C>, RuntimeFailure> {
207 let dependencies = self.app.dependencies(caller_instance)?;
208 dependencies
209 .bindings()
210 .iter()
211 .find(|binding| {
212 binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
213 })
214 .and_then(lenso_kernel::PluginDependency::event_handle)
215 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
216 .typed::<C>()
217 }
218
219 fn insert_stream<C: StreamCapability>(&self, session_id: u64, stream: NativeStream<C>) {
220 self.stream_sessions
221 .borrow_mut()
222 .insert((TypeId::of::<C>(), session_id), Box::new(Rc::new(stream)));
223 }
224
225 fn stream<C: StreamCapability>(
226 &self,
227 session_id: u64,
228 ) -> Result<Rc<NativeStream<C>>, RuntimeFailure> {
229 self.stream_sessions
230 .borrow()
231 .get(&(TypeId::of::<C>(), session_id))
232 .and_then(|stream| stream.downcast_ref::<Rc<NativeStream<C>>>())
233 .cloned()
234 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })
235 }
236
237 fn remove_stream<C: StreamCapability>(&self, session_id: u64) {
238 self.stream_sessions
239 .borrow_mut()
240 .remove(&(TypeId::of::<C>(), session_id));
241 }
242}
243
244#[derive(Clone, Debug, Default)]
246pub struct CrossLaneTransferCatalog {
247 requests: CrossLaneRequestCatalog,
248 interactions: CrossLaneInteractionCatalog,
249}
250
251impl CrossLaneTransferCatalog {
252 pub fn new() -> Self {
254 Self::default()
255 }
256
257 #[must_use]
259 pub fn with_request<C>(mut self, operations: &'static [&'static str]) -> Self
260 where
261 C: RequestCapability,
262 C::Request: Send,
263 C::Response: Send,
264 C::DomainError: Send,
265 {
266 self.requests = self.requests.with_request::<C>(operations);
267 self
268 }
269
270 #[must_use]
272 pub fn with_stream<C>(mut self, operations: &'static [&'static str]) -> Self
273 where
274 C: StreamCapability,
275 C::OpenRequest: Send,
276 C::Message: Send,
277 C::DomainError: Send,
278 {
279 self.interactions = self.interactions.with_stream::<C>(operations);
280 self
281 }
282
283 #[must_use]
285 pub fn with_event<C>(mut self, operations: &'static [&'static str]) -> Self
286 where
287 C: EventCapability,
288 C::Event: Send,
289 {
290 self.interactions = self.interactions.with_event::<C>(operations);
291 self
292 }
293
294 fn validate_plan(&self, plan: &ResolvedAppPlan) -> Result<(), ReplicatedRunnerError> {
295 self.requests.validate_plan(plan)?;
296 self.interactions.validate_plan(plan)
297 }
298}
299
300impl From<CrossLaneRequestCatalog> for CrossLaneTransferCatalog {
301 fn from(requests: CrossLaneRequestCatalog) -> Self {
302 Self {
303 requests,
304 interactions: CrossLaneInteractionCatalog::default(),
305 }
306 }
307}
308
309pub struct ReplicatedNativeApp {
311 request_routes: Arc<RequestRouteIndex>,
312 lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
313 diagnostics: Arc<LaneDiagnosticsState>,
314 terminal: Arc<ReplicatedTerminalState>,
315 epoch: Instant,
316}
317
318#[derive(Clone)]
319struct ReplicatedLaneRoute {
320 id: ExecutionLaneId,
321 commands: LaneSender,
322}
323
324#[derive(Clone)]
326pub struct ReplicatedAppRoute {
327 request_routes: Arc<RequestRouteIndex>,
328 lanes: BTreeMap<ExecutionLaneId, ReplicatedLaneRoute>,
329 diagnostics: Arc<LaneDiagnosticsState>,
330 terminal: Arc<ReplicatedTerminalState>,
331 epoch: Instant,
332}
333
334impl fmt::Debug for ReplicatedAppRoute {
335 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
336 formatter
337 .debug_struct("ReplicatedAppRoute")
338 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
339 .finish_non_exhaustive()
340 }
341}
342
343impl fmt::Debug for ReplicatedNativeApp {
344 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
345 formatter
346 .debug_struct("ReplicatedNativeApp")
347 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
348 .finish_non_exhaustive()
349 }
350}
351
352impl ReplicatedNativeApp {
353 pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
355 where
356 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
357 {
358 Self::start_fallible(plan, move |lane| Ok(adapters(lane)))
359 }
360
361 pub fn start_fallible<F>(
363 plan: ResolvedAppPlan,
364 adapters: F,
365 ) -> Result<Self, ReplicatedRunnerError>
366 where
367 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
368 {
369 Self::start_with_fallible_transfer_catalog(
370 plan,
371 adapters,
372 CrossLaneTransferCatalog::new(),
373 None,
374 )
375 }
376
377 pub fn start_fallible_with_timeout<F>(
379 plan: ResolvedAppPlan,
380 adapters: F,
381 ready_timeout: Duration,
382 ) -> Result<Self, ReplicatedRunnerError>
383 where
384 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
385 {
386 Self::start_with_fallible_transfer_catalog(
387 plan,
388 adapters,
389 CrossLaneTransferCatalog::new(),
390 Some(ready_timeout),
391 )
392 }
393
394 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
396 pub fn start_with_transfers<F>(
397 plan: ResolvedAppPlan,
398 adapters: F,
399 transfers: CrossLaneRequestCatalog,
400 ) -> Result<Self, ReplicatedRunnerError>
401 where
402 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
403 {
404 Self::start_with_transfer_catalog(plan, adapters, transfers.into())
405 }
406
407 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
409 pub fn start_with_transfer_catalog<F>(
410 plan: ResolvedAppPlan,
411 adapters: F,
412 transfers: CrossLaneTransferCatalog,
413 ) -> Result<Self, ReplicatedRunnerError>
414 where
415 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
416 {
417 Self::start_with_fallible_transfer_catalog(
418 plan,
419 move |lane| Ok(adapters(lane)),
420 transfers,
421 None,
422 )
423 }
424
425 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
427 pub fn start_with_fallible_transfer_catalog<F>(
428 plan: ResolvedAppPlan,
429 adapters: F,
430 transfers: CrossLaneTransferCatalog,
431 ready_timeout: Option<Duration>,
432 ) -> Result<Self, ReplicatedRunnerError>
433 where
434 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
435 {
436 plan.validate()
437 .map_err(|error| ReplicatedRunnerError::InvalidPlan {
438 detail: error.to_string(),
439 })?;
440 transfers.validate_plan(&plan)?;
441 let request_routes = Arc::new(index_request_routes(&plan));
442 let plan = Arc::new(plan);
443 let adapters = Arc::new(adapters);
444 let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
445 let terminal = Arc::new(ReplicatedTerminalState::default());
446 let epoch = Instant::now();
447 let mut receivers = BTreeMap::new();
448 let senders = plan
449 .execution_lanes()
450 .iter()
451 .map(|lane| {
452 let (sender, receiver) = mpsc::channel(64);
453 receivers.insert(lane.id().clone(), receiver);
454 (lane.id().clone(), sender)
455 })
456 .collect::<BTreeMap<_, _>>();
457 let routes = Arc::new(
458 senders
459 .iter()
460 .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
461 .collect::<BTreeMap<_, _>>(),
462 );
463 let projected = plan
464 .execution_lanes()
465 .iter()
466 .map(|lane| {
467 project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
468 })
469 .collect::<Result<Vec<_>, _>>()?;
470 let mut lanes = BTreeMap::new();
471 let mut startups = Vec::new();
472
473 for (lane_id, lane_plan) in projected {
474 let commands = senders
475 .get(&lane_id)
476 .expect("every declared lane has a command route")
477 .clone();
478 let receiver = receivers
479 .remove(&lane_id)
480 .expect("every declared lane has one command receiver");
481 let (shutdown, shutdown_request) = oneshot::channel();
482 let (started, startup) = std_mpsc::sync_channel(1);
483 let lane_adapters = Arc::clone(&adapters);
484 let lane_diagnostics = Arc::clone(&diagnostics);
485 let lane_terminal = Arc::clone(&terminal);
486 let proxy_adapter = LaneProxyAdapter::new(
487 Arc::clone(&plan),
488 transfers.clone(),
489 Arc::clone(&routes),
490 epoch,
491 );
492 let thread_lane = lane_id.clone();
493 let lane_thread = match thread::Builder::new()
494 .name(format!("lenso-lane-{}", lane_id.as_str()))
495 .spawn(move || {
496 let reported_lane = thread_lane.clone();
497 let result = catch_unwind(AssertUnwindSafe(|| {
498 run_lane(
499 thread_lane,
500 lane_plan,
501 receiver,
502 shutdown_request,
503 started,
504 lane_adapters,
505 proxy_adapter,
506 lane_diagnostics,
507 Arc::clone(&lane_terminal),
508 epoch,
509 );
510 }));
511 if result.is_err() {
512 lane_terminal.fail(ReplicatedRunnerError::LanePanicked {
513 lane: reported_lane.to_string(),
514 });
515 }
516 }) {
517 Ok(thread) => thread,
518 Err(error) => {
519 drop(receivers);
520 drop(routes);
521 drop(senders);
522 terminal.begin_shutdown();
523 stop_lanes(lanes);
524 return Err(ReplicatedRunnerError::LaneStartup {
525 lane: lane_id.to_string(),
526 detail: error.to_string(),
527 });
528 }
529 };
530 startups.push((lane_id.clone(), startup));
531 lanes.insert(
532 lane_id.clone(),
533 LaneHandle {
534 id: lane_id,
535 commands,
536 shutdown,
537 thread: lane_thread,
538 },
539 );
540 }
541
542 let ready_deadline = ready_timeout.and_then(|timeout| Instant::now().checked_add(timeout));
543 for (lane, startup) in startups {
544 let startup = if let Some(deadline) = ready_deadline {
545 startup.recv_timeout(deadline.saturating_duration_since(Instant::now()))
546 } else {
547 startup
548 .recv()
549 .map_err(|_| std_mpsc::RecvTimeoutError::Disconnected)
550 };
551 match startup {
552 Ok(Ok(())) => {}
553 Ok(Err(detail)) => {
554 drop(routes);
555 drop(senders);
556 terminal.begin_shutdown();
557 stop_lanes(lanes);
558 return Err(ReplicatedRunnerError::LaneStartup {
559 lane: lane.to_string(),
560 detail,
561 });
562 }
563 Err(error) => {
564 drop(routes);
565 drop(senders);
566 let failure = terminal.failure().unwrap_or_else(|| {
567 if error == std_mpsc::RecvTimeoutError::Timeout {
568 ReplicatedRunnerError::LaneStartup {
569 lane: lane.to_string(),
570 detail: "complete App Generation Ready Gate timed out".to_owned(),
571 }
572 } else {
573 ReplicatedRunnerError::LaneUnavailable {
574 lane: lane.to_string(),
575 }
576 }
577 });
578 terminal.begin_shutdown();
579 stop_lanes(lanes);
580 return Err(failure);
581 }
582 }
583 }
584
585 Ok(Self {
586 request_routes,
587 lanes,
588 diagnostics,
589 terminal,
590 epoch,
591 })
592 }
593
594 pub fn lane_count(&self) -> usize {
596 self.lanes.len()
597 }
598
599 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
601 self.diagnostics.snapshot()
602 }
603
604 pub fn is_failed(&self) -> bool {
606 self.terminal.is_failed()
607 }
608
609 pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
611 self.terminal.failure()
612 }
613
614 pub async fn wait_for_terminal(&self) -> ReplicatedRunnerError {
616 self.terminal.wait().await
617 }
618
619 pub fn route(&self) -> ReplicatedAppRoute {
621 ReplicatedAppRoute {
622 request_routes: Arc::clone(&self.request_routes),
623 lanes: self
624 .lanes
625 .iter()
626 .map(|(lane, handle)| {
627 (
628 lane.clone(),
629 ReplicatedLaneRoute {
630 id: handle.id.clone(),
631 commands: handle.commands.clone(),
632 },
633 )
634 })
635 .collect(),
636 diagnostics: Arc::clone(&self.diagnostics),
637 terminal: Arc::clone(&self.terminal),
638 epoch: self.epoch,
639 }
640 }
641}
642
643impl ReplicatedAppRoute {
644 fn ensure_running(&self) -> Result<(), RuntimeFailure> {
645 if let Some(failure) = self.terminal.failure() {
646 return Err(RuntimeFailure::Internal {
647 detail: failure.to_string(),
648 });
649 }
650 Ok(())
651 }
652
653 pub fn lane_count(&self) -> usize {
655 self.lanes.len()
656 }
657
658 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
660 self.diagnostics.snapshot()
661 }
662
663 pub fn is_failed(&self) -> bool {
665 self.terminal.is_failed()
666 }
667
668 pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
670 self.terminal.failure()
671 }
672
673 fn resolve_request_lane<C: RequestCapability>(
674 &self,
675 caller_instance: &str,
676 ) -> Result<(&ReplicatedLaneRoute, Arc<str>, Option<CrossLaneDiagnostics>), RuntimeFailure>
677 {
678 let route = resolve_planned_request_route(&self.request_routes, caller_instance, C::ID)?;
679 let lane =
680 self.lanes
681 .get(&route.provider_lane)
682 .ok_or_else(|| RuntimeFailure::Internal {
683 detail: format!("Execution Lane `{}` is unavailable", route.provider_lane),
684 })?;
685 let diagnostics = (route.consumer_lane != route.provider_lane).then(|| {
686 (
687 Arc::clone(&self.diagnostics),
688 route.consumer_lane.clone(),
689 Arc::clone(&route.provider_instance),
690 )
691 });
692 Ok((lane, Arc::clone(&route.caller_instance), diagnostics))
693 }
694
695 pub async fn invoke<C: RequestCapability>(
697 &self,
698 caller_instance: &str,
699 operation: &str,
700 request: C::Request,
701 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
702 where
703 C::Request: Send,
704 C::Response: Send,
705 C::DomainError: Send,
706 {
707 self.invoke_with_options::<C>(
708 caller_instance,
709 operation,
710 request,
711 LaneInvocationOptions::new(),
712 )
713 .await
714 }
715
716 pub async fn invoke_with_options<C: RequestCapability>(
721 &self,
722 caller_instance: &str,
723 operation: &str,
724 request: C::Request,
725 options: LaneInvocationOptions,
726 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
727 where
728 C::Request: Send,
729 C::Response: Send,
730 C::DomainError: Send,
731 {
732 self.ensure_running()?;
733 let (lane, caller_instance, cross_lane_diagnostics) =
736 self.resolve_request_lane::<C>(caller_instance)?;
737 let operation = operation.to_owned();
738 let deadline = options
739 .timeout
740 .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
741 let admission_timeout = options.timeout;
742 let admission_cancellation = options.cancellation.clone();
743 let controlled = deadline.is_some() || admission_cancellation.is_some();
744 let (completed, completion) = oneshot::channel();
745 let (started, start) = if controlled {
746 let (started, start) = oneshot::channel();
747 (Some(started), Some(start))
748 } else {
749 (None, None)
750 };
751 let task = Box::new(move |lane: LaneRuntime| {
752 if let Some((diagnostics, caller_lane, provider_instance)) = cross_lane_diagnostics {
753 diagnostics.record_invocation(&caller_lane, &caller_instance, &provider_instance);
754 }
755 tokio::task::spawn_local(async move {
756 let handle = match lane.request_handle::<C>(&caller_instance) {
757 Ok(handle) => handle,
758 Err(error) => {
759 let _ = completed.send(Err(error));
760 return;
761 }
762 };
763 let cancellation = CancellationToken::new();
764 let external_cancellation = options.cancellation;
765 if external_cancellation
766 .as_ref()
767 .is_some_and(LaneCancellationToken::is_cancelled)
768 {
769 cancellation.cancel();
770 }
771 let invocation = if deadline.is_some() || external_cancellation.is_some() {
772 let context = lane.app.invocation_context(deadline, cancellation.clone());
773 if let Some(started) = started {
774 let _ = started.send(());
775 }
776 Either::Left(handle.invoke_with_context(&operation, context, request))
777 } else {
778 Either::Right(handle.invoke(&operation, request))
779 };
780 tokio::pin!(invocation);
781 let result = if let Some(external_cancellation) = external_cancellation {
782 tokio::select! {
783 result = &mut invocation => result,
784 () = external_cancellation.cancelled() => {
785 cancellation.cancel();
786 invocation.await
787 }
788 }
789 } else {
790 invocation.await
791 };
792 let _ = completed.send(result);
793 });
794 });
795 if let Some(start) = start {
796 return admission::dispatch_controlled(
797 &lane.id,
798 &lane.commands,
799 task,
800 start,
801 completion,
802 admission_timeout,
803 admission_cancellation,
804 )
805 .await?;
806 }
807 lane.commands
808 .send(task)
809 .await
810 .map_err(|_| RuntimeFailure::Internal {
811 detail: format!("Execution Lane `{}` is unavailable", lane.id),
812 })?;
813 completion.await.map_err(|_| RuntimeFailure::Internal {
814 detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
815 })?
816 }
817}
818
819impl ReplicatedNativeApp {
820 pub async fn invoke<C: RequestCapability>(
822 &self,
823 caller_instance: &str,
824 operation: &str,
825 request: C::Request,
826 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
827 where
828 C::Request: Send,
829 C::Response: Send,
830 C::DomainError: Send,
831 {
832 self.route()
833 .invoke::<C>(caller_instance, operation, request)
834 .await
835 }
836
837 pub async fn invoke_with_options<C: RequestCapability>(
839 &self,
840 caller_instance: &str,
841 operation: &str,
842 request: C::Request,
843 options: LaneInvocationOptions,
844 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
845 where
846 C::Request: Send,
847 C::Response: Send,
848 C::DomainError: Send,
849 {
850 self.route()
851 .invoke_with_options::<C>(caller_instance, operation, request, options)
852 .await
853 }
854
855 pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
857 self.terminal.begin_shutdown();
858 let mut completions = Vec::new();
859 let mut threads = Vec::new();
860 let mut first_error = self.terminal.failure();
861 for (_, lane) in self.lanes {
862 let LaneHandle {
863 id,
864 commands,
865 shutdown,
866 thread,
867 } = lane;
868 let (completed, completion) = oneshot::channel();
869 if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
870 completions.push((id.clone(), completion));
871 } else if first_error.is_none() {
872 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
873 lane: id.to_string(),
874 });
875 }
876 drop(commands);
877 threads.push((id, thread));
878 }
879
880 for (lane, completion) in completions {
881 match completion.await {
882 Ok(ShutdownOutcome::Clean) => {}
883 Ok(outcome) if first_error.is_none() => {
884 first_error = Some(ReplicatedRunnerError::LaneShutdown {
885 lane: lane.to_string(),
886 outcome,
887 });
888 }
889 Err(_) if first_error.is_none() => {
890 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
891 lane: lane.to_string(),
892 });
893 }
894 _ => {}
895 }
896 }
897 for (lane, thread) in threads {
898 if thread.join().is_err() && first_error.is_none() {
899 first_error = Some(ReplicatedRunnerError::LanePanicked {
900 lane: lane.to_string(),
901 });
902 }
903 }
904 match first_error {
905 Some(error) => Err(error),
906 None => Ok(()),
907 }
908 }
909}
910
911fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
912 let mut threads = Vec::new();
913 for (_, lane) in lanes {
914 let (completed, _) = oneshot::channel();
915 let _ = lane.shutdown.send(LaneShutdown {
916 timeout: Duration::from_secs(1),
917 completed,
918 });
919 threads.push(lane.thread);
920 }
921 for thread in threads {
922 let _ = thread.join();
923 }
924}
925
926fn index_request_routes(plan: &ResolvedAppPlan) -> RequestRouteIndex {
927 let mut routes = RequestRouteIndex::new();
928 for binding in plan.capability_bindings() {
929 let consumer = plan
930 .plugin_instance(binding.consumer_instance())
931 .expect("validated binding consumer should exist");
932 let provider = plan
933 .plugin_instance(binding.provider_instance())
934 .expect("validated binding provider should exist");
935 let capabilities = routes
936 .entry(binding.consumer_instance().to_owned())
937 .or_default();
938 let route = capabilities
939 .entry(binding.capability_id().to_owned())
940 .or_insert_with(|| PlannedRequestRoute {
941 caller_instance: Arc::from(binding.consumer_instance()),
942 provider_instance: Arc::from(binding.provider_instance()),
943 consumer_lane: consumer.execution_lane().clone(),
944 provider_lane: provider.execution_lane().clone(),
945 providers: 0,
946 });
947 route.providers += 1;
948 }
949 routes
950}
951
952fn resolve_planned_request_route<'a>(
953 routes: &'a RequestRouteIndex,
954 caller_instance: &str,
955 capability: &'static str,
956) -> Result<&'a PlannedRequestRoute, RuntimeFailure> {
957 let route = routes
958 .get(caller_instance)
959 .and_then(|capabilities| capabilities.get(capability))
960 .ok_or(RuntimeFailure::Unavailable { capability })?;
961 if route.providers != 1 {
962 return Err(RuntimeFailure::AmbiguousBinding {
963 capability,
964 providers: route.providers,
965 });
966 }
967 Ok(route)
968}
969
970#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
971fn run_lane<F>(
972 lane: ExecutionLaneId,
973 plan: ResolvedAppPlan,
974 mut commands: mpsc::Receiver<LaneTask>,
975 mut shutdown: oneshot::Receiver<LaneShutdown>,
976 started: std_mpsc::SyncSender<Result<(), String>>,
977 adapters: Arc<F>,
978 proxy_adapter: LaneProxyAdapter,
979 diagnostics: Arc<LaneDiagnosticsState>,
980 terminal: Arc<ReplicatedTerminalState>,
981 epoch: Instant,
982) where
983 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
984{
985 let runtime = match tokio::runtime::Builder::new_current_thread()
986 .enable_all()
987 .build()
988 {
989 Ok(runtime) => runtime,
990 Err(error) => {
991 let _ = started.send(Err(error.to_string()));
992 return;
993 }
994 };
995 let local = tokio::task::LocalSet::new();
996 local.block_on(&runtime, async move {
997 let cpu_started = ThreadTime::now();
998 let catalog = match adapters(&lane) {
999 Ok(catalog) => match catalog.with_adapter(proxy_adapter) {
1000 Ok(catalog) => catalog,
1001 Err(error) => {
1002 let _ = started.send(Err(error.to_string()));
1003 return;
1004 }
1005 },
1006 Err(detail) => {
1007 let _ = started.send(Err(detail));
1008 return;
1009 }
1010 };
1011 let driver = TokioDriver::with_epoch(epoch);
1012 let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
1013 LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
1014 ));
1015 let start = lenso_kernel::Kernel::start_with_diagnostics(
1016 plan,
1017 driver,
1018 catalog,
1019 runtime_diagnostics,
1020 );
1021 tokio::pin!(start);
1022 let app = match tokio::select! {
1023 result = &mut start => Some(result),
1024 _ = &mut shutdown => None,
1025 } {
1026 Some(Ok(app)) => app,
1027 Some(Err(error)) => {
1028 let _ = started.send(Err(format!("{error:?}")));
1029 return;
1030 }
1031 None => {
1032 let _ = started.send(Err("lane startup cancelled before Ready".to_owned()));
1033 return;
1034 }
1035 };
1036 let lane_runtime = LaneRuntime::new(app.clone());
1037 let _ = started.send(Ok(()));
1038 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
1039 let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
1040 let terminal_monitor = tokio::task::spawn_local(monitor_lane_failure(
1041 lane.clone(),
1042 app.clone(),
1043 Arc::clone(&terminal),
1044 ));
1045 let terminal_failure = terminal.wait();
1046 tokio::pin!(terminal_failure);
1047
1048 loop {
1049 tokio::select! {
1050 biased;
1051 shutdown = &mut shutdown => {
1052 terminal_monitor.abort();
1053 match shutdown {
1054 Ok(LaneShutdown { timeout, completed }) => {
1055 let outcome = app.shutdown(timeout).await;
1056 let _ = completed.send(outcome);
1057 }
1058 Err(_) => {
1059 let _ = app.shutdown(Duration::from_secs(1)).await;
1060 }
1061 }
1062 break;
1063 }
1064 _ = &mut terminal_failure => {
1065 terminal_monitor.abort();
1066 let _ = app.shutdown(Duration::from_secs(1)).await;
1067 break;
1068 },
1069 command = commands.recv() => if let Some(task) = command {
1070 task(lane_runtime.clone());
1071 } else {
1072 terminal_monitor.abort();
1073 if !terminal.is_stopping() {
1074 terminal.fail(ReplicatedRunnerError::LaneUnavailable {
1075 lane: lane.to_string(),
1076 });
1077 }
1078 let _ = app.shutdown(Duration::from_secs(1)).await;
1079 break;
1080 },
1081 _ = sample_interval.tick() => {
1082 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
1083 }
1084 }
1085 }
1086 });
1087}
1088
1089async fn monitor_lane_failure(
1090 lane: ExecutionLaneId,
1091 app: NativeApp,
1092 terminal: Arc<ReplicatedTerminalState>,
1093) {
1094 let mut failure_interval = tokio::time::interval(Duration::from_millis(10));
1095 loop {
1096 failure_interval.tick().await;
1097 if let Some(error) = app.terminal_failure() {
1098 terminal.fail(ReplicatedRunnerError::LaneRuntimeFailure {
1099 lane: lane.to_string(),
1100 error,
1101 });
1102 return;
1103 }
1104 if terminal.is_failed() {
1105 return;
1106 }
1107 }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112 use std::time::Duration;
1113
1114 use lenso_app_plan::{
1115 AppComposition, CapabilityBinding, CapabilityEndpointPlan, CapabilityRequirementPlan,
1116 ExecutionLaneId, ExecutionLanePlan, PluginInstancePlan,
1117 };
1118 use lenso_kernel::{ExecutionAdapterCatalog, RuntimeFailure};
1119
1120 use super::{
1121 PlannedRequestRoute, ReplicatedNativeApp, ReplicatedRunnerError, RequestRouteIndex,
1122 index_request_routes, resolve_planned_request_route,
1123 };
1124
1125 const ROUTE_CAPABILITY: &str = "test.route@1";
1126 const ROUTE_VERSION: &str = "1.0.0";
1127
1128 #[test]
1129 fn request_route_index_preserves_missing_single_and_ambiguous_semantics() {
1130 let plan = AppComposition::new(
1131 vec![
1132 PluginInstancePlan::new("single-consumer", "fixture.consumer").with_requirement(
1133 CapabilityRequirementPlan::one(ROUTE_CAPABILITY, ROUTE_VERSION),
1134 ),
1135 PluginInstancePlan::new("many-consumer", "fixture.consumer").with_requirement(
1136 CapabilityRequirementPlan::many(ROUTE_CAPABILITY, ROUTE_VERSION),
1137 ),
1138 PluginInstancePlan::new("provider-a", "fixture.provider").with_capability(
1139 CapabilityEndpointPlan::new(ROUTE_CAPABILITY, ROUTE_VERSION, ["route"]),
1140 ),
1141 PluginInstancePlan::new("provider-b", "fixture.provider").with_capability(
1142 CapabilityEndpointPlan::new(ROUTE_CAPABILITY, ROUTE_VERSION, ["route"]),
1143 ),
1144 ],
1145 vec![
1146 CapabilityBinding::new(
1147 "single-consumer",
1148 ROUTE_CAPABILITY,
1149 ROUTE_VERSION,
1150 "provider-a",
1151 ),
1152 CapabilityBinding::new(
1153 "many-consumer",
1154 ROUTE_CAPABILITY,
1155 ROUTE_VERSION,
1156 "provider-a",
1157 ),
1158 CapabilityBinding::new(
1159 "many-consumer",
1160 ROUTE_CAPABILITY,
1161 ROUTE_VERSION,
1162 "provider-b",
1163 ),
1164 ],
1165 )
1166 .resolve()
1167 .expect("route fixture should resolve");
1168 let routes = index_request_routes(&plan);
1169
1170 assert_eq!(
1171 resolve_planned_request_route(&routes, "missing", ROUTE_CAPABILITY),
1172 Err(RuntimeFailure::Unavailable {
1173 capability: ROUTE_CAPABILITY
1174 })
1175 );
1176 let single = resolve_planned_request_route(&routes, "single-consumer", ROUTE_CAPABILITY)
1177 .expect("the singular binding should resolve");
1178 assert_eq!(single.providers, 1);
1179 assert_eq!(&*single.provider_instance, "provider-a");
1180 assert_eq!(
1181 resolve_planned_request_route(&routes, "many-consumer", ROUTE_CAPABILITY),
1182 Err(RuntimeFailure::AmbiguousBinding {
1183 capability: ROUTE_CAPABILITY,
1184 providers: 2,
1185 })
1186 );
1187 }
1188
1189 #[test]
1190 fn large_request_route_index_is_self_contained_after_plan_resolution() {
1191 const BINDINGS: usize = 512;
1192 let mut instances = Vec::with_capacity(BINDINGS + 1);
1193 let mut bindings = Vec::with_capacity(BINDINGS);
1194 instances.push(
1195 PluginInstancePlan::new("provider", "fixture.provider").with_capability(
1196 CapabilityEndpointPlan::new(ROUTE_CAPABILITY, ROUTE_VERSION, ["route"]),
1197 ),
1198 );
1199 for index in 0..BINDINGS {
1200 let consumer = format!("consumer-{index}");
1201 instances.push(
1202 PluginInstancePlan::new(&consumer, "fixture.consumer").with_requirement(
1203 CapabilityRequirementPlan::one(ROUTE_CAPABILITY, ROUTE_VERSION),
1204 ),
1205 );
1206 bindings.push(CapabilityBinding::new(
1207 consumer,
1208 ROUTE_CAPABILITY,
1209 ROUTE_VERSION,
1210 "provider",
1211 ));
1212 }
1213 let routes = index_request_routes(
1214 &AppComposition::new(instances, bindings)
1215 .resolve()
1216 .expect("large route fixture should resolve"),
1217 );
1218
1219 assert_eq!(routes.len(), BINDINGS);
1220 for index in 0..BINDINGS {
1221 let route = resolve_planned_request_route(
1222 &routes,
1223 &format!("consumer-{index}"),
1224 ROUTE_CAPABILITY,
1225 )
1226 .expect("every indexed route should remain available");
1227 assert_eq!(&*route.provider_instance, "provider");
1228 assert_eq!(route.providers, 1);
1229 }
1230 }
1231
1232 #[test]
1235 #[ignore = "route-index microbenchmark; run explicitly when changing replicated routing"]
1236 fn indexed_route_lookup_benchmark() {
1237 const LOOKUPS: usize = 5_000_000;
1238
1239 fn routes(bindings: usize) -> RequestRouteIndex {
1240 (0..bindings)
1241 .map(|index| {
1242 (
1243 format!("consumer-{index}"),
1244 [(
1245 ROUTE_CAPABILITY.to_owned(),
1246 PlannedRequestRoute {
1247 caller_instance: format!("consumer-{index}").into(),
1248 provider_instance: "provider".into(),
1249 consumer_lane: ExecutionLaneId::new("frontend"),
1250 provider_lane: ExecutionLaneId::new("workers"),
1251 providers: 1,
1252 },
1253 )]
1254 .into_iter()
1255 .collect(),
1256 )
1257 })
1258 .collect()
1259 }
1260
1261 fn nanoseconds_per_lookup(routes: &RequestRouteIndex, caller: &str) -> f64 {
1262 let started = std::time::Instant::now();
1263 for _ in 0..LOOKUPS {
1264 let route = resolve_planned_request_route(routes, caller, ROUTE_CAPABILITY)
1265 .expect("indexed benchmark route should resolve");
1266 std::hint::black_box(route);
1267 }
1268 started.elapsed().as_secs_f64() * 1_000_000_000.0
1269 / f64::from(u32::try_from(LOOKUPS).expect("lookup count fits u32"))
1270 }
1271
1272 let small = routes(1);
1273 let large = routes(8_192);
1274 let small_ns = nanoseconds_per_lookup(&small, "consumer-0");
1275 let large_ns = nanoseconds_per_lookup(&large, "consumer-8191");
1276 println!(
1277 "{{\"lookups\":{LOOKUPS},\"small_bindings\":1,\"large_bindings\":8192,\"small_ns_per_lookup\":{small_ns:.3},\"large_ns_per_lookup\":{large_ns:.3},\"large_to_small_ratio\":{:.3}}}",
1278 large_ns / small_ns,
1279 );
1280 }
1281
1282 #[tokio::test(flavor = "current_thread")]
1283 async fn one_lane_panic_makes_the_replicated_app_terminal_and_stops_its_peers() {
1284 let plan = AppComposition::new(Vec::new(), Vec::new())
1285 .with_execution_lanes(vec![
1286 ExecutionLanePlan::new("lane-a"),
1287 ExecutionLanePlan::new("lane-b"),
1288 ])
1289 .resolve()
1290 .expect("the empty two-lane Plan should resolve");
1291 let app = ReplicatedNativeApp::start(plan, |_| ExecutionAdapterCatalog::new())
1292 .expect("both empty Kernel lanes should start");
1293 let peer_commands = app
1294 .lanes
1295 .get(&ExecutionLaneId::new("lane-b"))
1296 .expect("lane-b should exist")
1297 .commands
1298 .clone();
1299 let flooding =
1300 tokio::spawn(
1301 async move { while peer_commands.send(Box::new(|_| {})).await.is_ok() {} },
1302 );
1303 app.lanes
1304 .get(&ExecutionLaneId::new("lane-a"))
1305 .expect("lane-a should exist")
1306 .commands
1307 .send(Box::new(|_| panic!("injected lane panic")))
1308 .await
1309 .expect("lane-a should accept the injected task");
1310
1311 let failure = tokio::time::timeout(Duration::from_secs(1), app.wait_for_terminal())
1312 .await
1313 .expect("the lane panic should become terminal promptly");
1314 assert_eq!(
1315 failure,
1316 ReplicatedRunnerError::LanePanicked {
1317 lane: "lane-a".to_owned(),
1318 }
1319 );
1320 assert!(app.is_failed());
1321 assert_eq!(app.terminal_failure(), Some(failure.clone()));
1322 assert_eq!(
1323 tokio::time::timeout(Duration::from_secs(1), app.shutdown(Duration::from_secs(1)))
1324 .await
1325 .expect("a saturated peer lane should still observe terminal failure"),
1326 Err(failure)
1327 );
1328 flooding
1329 .await
1330 .expect("the peer command producer should stop when the lane closes");
1331 }
1332}