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::{CapabilityBinding, 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, String);
120
121struct LaneShutdown {
122 timeout: Duration,
123 completed: oneshot::Sender<ShutdownOutcome>,
124}
125
126struct LaneHandle {
127 id: ExecutionLaneId,
128 commands: LaneSender,
129 shutdown: oneshot::Sender<LaneShutdown>,
130 thread: thread::JoinHandle<()>,
131}
132
133type TypedRequestHandles = HashMap<String, Box<dyn Any>>;
134type TypedStreamSessions = HashMap<(TypeId, u64), Box<dyn Any>>;
135
136#[derive(Clone)]
137struct LaneRuntime {
138 app: NativeApp,
139 request_handles: Rc<RefCell<HashMap<TypeId, TypedRequestHandles>>>,
140 stream_sessions: Rc<RefCell<TypedStreamSessions>>,
141}
142
143impl LaneRuntime {
144 fn new(app: NativeApp) -> Self {
145 Self {
146 app,
147 request_handles: Rc::new(RefCell::new(HashMap::new())),
148 stream_sessions: Rc::new(RefCell::new(HashMap::new())),
149 }
150 }
151
152 fn request_handle<C: RequestCapability>(
153 &self,
154 caller_instance: &str,
155 ) -> Result<Rc<NativeRequestHandle<C>>, RuntimeFailure> {
156 let capability = TypeId::of::<C>();
157 if let Some(handle) = self
158 .request_handles
159 .borrow()
160 .get(&capability)
161 .and_then(|handles| handles.get(caller_instance))
162 .and_then(|handle| handle.downcast_ref::<Rc<NativeRequestHandle<C>>>())
163 {
164 return Ok(handle.clone());
165 }
166 let handle = Rc::new(self.app.handle::<C>(caller_instance)?);
167 self.request_handles
168 .borrow_mut()
169 .entry(capability)
170 .or_default()
171 .insert(caller_instance.to_owned(), Box::new(handle.clone()));
172 Ok(handle)
173 }
174
175 fn stream_handle<C: StreamCapability>(
176 &self,
177 caller_instance: &str,
178 provider_instance: &str,
179 ) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
180 let dependencies = self.app.dependencies(caller_instance)?;
181 dependencies
182 .bindings()
183 .iter()
184 .find(|binding| {
185 binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
186 })
187 .and_then(lenso_kernel::ModuleDependency::stream_handle)
188 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
189 .typed::<C>()
190 }
191
192 fn event_handle<C: EventCapability>(
193 &self,
194 caller_instance: &str,
195 provider_instance: &str,
196 ) -> Result<NativeEventHandle<C>, RuntimeFailure> {
197 let dependencies = self.app.dependencies(caller_instance)?;
198 dependencies
199 .bindings()
200 .iter()
201 .find(|binding| {
202 binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
203 })
204 .and_then(lenso_kernel::ModuleDependency::event_handle)
205 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
206 .typed::<C>()
207 }
208
209 fn insert_stream<C: StreamCapability>(&self, session_id: u64, stream: NativeStream<C>) {
210 self.stream_sessions
211 .borrow_mut()
212 .insert((TypeId::of::<C>(), session_id), Box::new(Rc::new(stream)));
213 }
214
215 fn stream<C: StreamCapability>(
216 &self,
217 session_id: u64,
218 ) -> Result<Rc<NativeStream<C>>, RuntimeFailure> {
219 self.stream_sessions
220 .borrow()
221 .get(&(TypeId::of::<C>(), session_id))
222 .and_then(|stream| stream.downcast_ref::<Rc<NativeStream<C>>>())
223 .cloned()
224 .ok_or(RuntimeFailure::Unavailable { capability: C::ID })
225 }
226
227 fn remove_stream<C: StreamCapability>(&self, session_id: u64) {
228 self.stream_sessions
229 .borrow_mut()
230 .remove(&(TypeId::of::<C>(), session_id));
231 }
232}
233
234#[derive(Clone, Debug, Default)]
236pub struct CrossLaneTransferCatalog {
237 requests: CrossLaneRequestCatalog,
238 interactions: CrossLaneInteractionCatalog,
239}
240
241impl CrossLaneTransferCatalog {
242 pub fn new() -> Self {
244 Self::default()
245 }
246
247 #[must_use]
249 pub fn with_request<C>(mut self, operations: &'static [&'static str]) -> Self
250 where
251 C: RequestCapability,
252 C::Request: Send,
253 C::Response: Send,
254 C::DomainError: Send,
255 {
256 self.requests = self.requests.with_request::<C>(operations);
257 self
258 }
259
260 #[must_use]
262 pub fn with_stream<C>(mut self, operations: &'static [&'static str]) -> Self
263 where
264 C: StreamCapability,
265 C::OpenRequest: Send,
266 C::Message: Send,
267 C::DomainError: Send,
268 {
269 self.interactions = self.interactions.with_stream::<C>(operations);
270 self
271 }
272
273 #[must_use]
275 pub fn with_event<C>(mut self, operations: &'static [&'static str]) -> Self
276 where
277 C: EventCapability,
278 C::Event: Send,
279 {
280 self.interactions = self.interactions.with_event::<C>(operations);
281 self
282 }
283
284 fn validate_plan(&self, plan: &ResolvedAppPlan) -> Result<(), ReplicatedRunnerError> {
285 self.requests.validate_plan(plan)?;
286 self.interactions.validate_plan(plan)
287 }
288}
289
290impl From<CrossLaneRequestCatalog> for CrossLaneTransferCatalog {
291 fn from(requests: CrossLaneRequestCatalog) -> Self {
292 Self {
293 requests,
294 interactions: CrossLaneInteractionCatalog::default(),
295 }
296 }
297}
298
299pub struct ReplicatedNativeApp {
301 plan: Arc<ResolvedAppPlan>,
302 lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
303 diagnostics: Arc<LaneDiagnosticsState>,
304 terminal: Arc<ReplicatedTerminalState>,
305 epoch: Instant,
306}
307
308#[derive(Clone)]
309struct ReplicatedLaneRoute {
310 id: ExecutionLaneId,
311 commands: LaneSender,
312}
313
314#[derive(Clone)]
316pub struct ReplicatedAppRoute {
317 plan: Arc<ResolvedAppPlan>,
318 lanes: BTreeMap<ExecutionLaneId, ReplicatedLaneRoute>,
319 diagnostics: Arc<LaneDiagnosticsState>,
320 terminal: Arc<ReplicatedTerminalState>,
321 epoch: Instant,
322}
323
324impl fmt::Debug for ReplicatedAppRoute {
325 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
326 formatter
327 .debug_struct("ReplicatedAppRoute")
328 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
329 .finish_non_exhaustive()
330 }
331}
332
333impl fmt::Debug for ReplicatedNativeApp {
334 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
335 formatter
336 .debug_struct("ReplicatedNativeApp")
337 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
338 .finish_non_exhaustive()
339 }
340}
341
342impl ReplicatedNativeApp {
343 pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
345 where
346 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
347 {
348 Self::start_fallible(plan, move |lane| Ok(adapters(lane)))
349 }
350
351 pub fn start_fallible<F>(
353 plan: ResolvedAppPlan,
354 adapters: F,
355 ) -> Result<Self, ReplicatedRunnerError>
356 where
357 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
358 {
359 Self::start_with_fallible_transfer_catalog(
360 plan,
361 adapters,
362 CrossLaneTransferCatalog::new(),
363 None,
364 )
365 }
366
367 pub fn start_fallible_with_timeout<F>(
369 plan: ResolvedAppPlan,
370 adapters: F,
371 ready_timeout: Duration,
372 ) -> Result<Self, ReplicatedRunnerError>
373 where
374 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
375 {
376 Self::start_with_fallible_transfer_catalog(
377 plan,
378 adapters,
379 CrossLaneTransferCatalog::new(),
380 Some(ready_timeout),
381 )
382 }
383
384 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
386 pub fn start_with_transfers<F>(
387 plan: ResolvedAppPlan,
388 adapters: F,
389 transfers: CrossLaneRequestCatalog,
390 ) -> Result<Self, ReplicatedRunnerError>
391 where
392 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
393 {
394 Self::start_with_transfer_catalog(plan, adapters, transfers.into())
395 }
396
397 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
399 pub fn start_with_transfer_catalog<F>(
400 plan: ResolvedAppPlan,
401 adapters: F,
402 transfers: CrossLaneTransferCatalog,
403 ) -> Result<Self, ReplicatedRunnerError>
404 where
405 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
406 {
407 Self::start_with_fallible_transfer_catalog(
408 plan,
409 move |lane| Ok(adapters(lane)),
410 transfers,
411 None,
412 )
413 }
414
415 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
417 pub fn start_with_fallible_transfer_catalog<F>(
418 plan: ResolvedAppPlan,
419 adapters: F,
420 transfers: CrossLaneTransferCatalog,
421 ready_timeout: Option<Duration>,
422 ) -> Result<Self, ReplicatedRunnerError>
423 where
424 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
425 {
426 plan.validate()
427 .map_err(|error| ReplicatedRunnerError::InvalidPlan {
428 detail: error.to_string(),
429 })?;
430 transfers.validate_plan(&plan)?;
431 let plan = Arc::new(plan);
432 let adapters = Arc::new(adapters);
433 let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
434 let terminal = Arc::new(ReplicatedTerminalState::default());
435 let epoch = Instant::now();
436 let mut receivers = BTreeMap::new();
437 let senders = plan
438 .execution_lanes()
439 .iter()
440 .map(|lane| {
441 let (sender, receiver) = mpsc::channel(64);
442 receivers.insert(lane.id().clone(), receiver);
443 (lane.id().clone(), sender)
444 })
445 .collect::<BTreeMap<_, _>>();
446 let routes = Arc::new(
447 senders
448 .iter()
449 .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
450 .collect::<BTreeMap<_, _>>(),
451 );
452 let projected = plan
453 .execution_lanes()
454 .iter()
455 .map(|lane| {
456 project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
457 })
458 .collect::<Result<Vec<_>, _>>()?;
459 let mut lanes = BTreeMap::new();
460 let mut startups = Vec::new();
461
462 for (lane_id, lane_plan) in projected {
463 let commands = senders
464 .get(&lane_id)
465 .expect("every declared lane has a command route")
466 .clone();
467 let receiver = receivers
468 .remove(&lane_id)
469 .expect("every declared lane has one command receiver");
470 let (shutdown, shutdown_request) = oneshot::channel();
471 let (started, startup) = std_mpsc::sync_channel(1);
472 let lane_adapters = Arc::clone(&adapters);
473 let lane_diagnostics = Arc::clone(&diagnostics);
474 let lane_terminal = Arc::clone(&terminal);
475 let proxy_adapter = LaneProxyAdapter::new(
476 Arc::clone(&plan),
477 transfers.clone(),
478 Arc::clone(&routes),
479 epoch,
480 );
481 let thread_lane = lane_id.clone();
482 let lane_thread = match thread::Builder::new()
483 .name(format!("lenso-lane-{}", lane_id.as_str()))
484 .spawn(move || {
485 let reported_lane = thread_lane.clone();
486 let result = catch_unwind(AssertUnwindSafe(|| {
487 run_lane(
488 thread_lane,
489 lane_plan,
490 receiver,
491 shutdown_request,
492 started,
493 lane_adapters,
494 proxy_adapter,
495 lane_diagnostics,
496 Arc::clone(&lane_terminal),
497 epoch,
498 );
499 }));
500 if result.is_err() {
501 lane_terminal.fail(ReplicatedRunnerError::LanePanicked {
502 lane: reported_lane.to_string(),
503 });
504 }
505 }) {
506 Ok(thread) => thread,
507 Err(error) => {
508 drop(receivers);
509 drop(routes);
510 drop(senders);
511 terminal.begin_shutdown();
512 stop_lanes(lanes);
513 return Err(ReplicatedRunnerError::LaneStartup {
514 lane: lane_id.to_string(),
515 detail: error.to_string(),
516 });
517 }
518 };
519 startups.push((lane_id.clone(), startup));
520 lanes.insert(
521 lane_id.clone(),
522 LaneHandle {
523 id: lane_id,
524 commands,
525 shutdown,
526 thread: lane_thread,
527 },
528 );
529 }
530
531 let ready_deadline = ready_timeout.and_then(|timeout| Instant::now().checked_add(timeout));
532 for (lane, startup) in startups {
533 let startup = if let Some(deadline) = ready_deadline {
534 startup.recv_timeout(deadline.saturating_duration_since(Instant::now()))
535 } else {
536 startup
537 .recv()
538 .map_err(|_| std_mpsc::RecvTimeoutError::Disconnected)
539 };
540 match startup {
541 Ok(Ok(())) => {}
542 Ok(Err(detail)) => {
543 drop(routes);
544 drop(senders);
545 terminal.begin_shutdown();
546 stop_lanes(lanes);
547 return Err(ReplicatedRunnerError::LaneStartup {
548 lane: lane.to_string(),
549 detail,
550 });
551 }
552 Err(error) => {
553 drop(routes);
554 drop(senders);
555 let failure = terminal.failure().unwrap_or_else(|| {
556 if error == std_mpsc::RecvTimeoutError::Timeout {
557 ReplicatedRunnerError::LaneStartup {
558 lane: lane.to_string(),
559 detail: "complete App Generation Ready Gate timed out".to_owned(),
560 }
561 } else {
562 ReplicatedRunnerError::LaneUnavailable {
563 lane: lane.to_string(),
564 }
565 }
566 });
567 terminal.begin_shutdown();
568 stop_lanes(lanes);
569 return Err(failure);
570 }
571 }
572 }
573
574 Ok(Self {
575 plan,
576 lanes,
577 diagnostics,
578 terminal,
579 epoch,
580 })
581 }
582
583 pub fn lane_count(&self) -> usize {
585 self.lanes.len()
586 }
587
588 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
590 self.diagnostics.snapshot()
591 }
592
593 pub fn is_failed(&self) -> bool {
595 self.terminal.is_failed()
596 }
597
598 pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
600 self.terminal.failure()
601 }
602
603 pub async fn wait_for_terminal(&self) -> ReplicatedRunnerError {
605 self.terminal.wait().await
606 }
607
608 pub fn route(&self) -> ReplicatedAppRoute {
610 ReplicatedAppRoute {
611 plan: Arc::clone(&self.plan),
612 lanes: self
613 .lanes
614 .iter()
615 .map(|(lane, handle)| {
616 (
617 lane.clone(),
618 ReplicatedLaneRoute {
619 id: handle.id.clone(),
620 commands: handle.commands.clone(),
621 },
622 )
623 })
624 .collect(),
625 diagnostics: Arc::clone(&self.diagnostics),
626 terminal: Arc::clone(&self.terminal),
627 epoch: self.epoch,
628 }
629 }
630}
631
632impl ReplicatedAppRoute {
633 fn ensure_running(&self) -> Result<(), RuntimeFailure> {
634 if let Some(failure) = self.terminal.failure() {
635 return Err(RuntimeFailure::Internal {
636 detail: failure.to_string(),
637 });
638 }
639 Ok(())
640 }
641
642 pub fn lane_count(&self) -> usize {
644 self.lanes.len()
645 }
646
647 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
649 self.diagnostics.snapshot()
650 }
651
652 pub fn is_failed(&self) -> bool {
654 self.terminal.is_failed()
655 }
656
657 pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
659 self.terminal.failure()
660 }
661
662 fn resolve_request_lane<C: RequestCapability>(
663 &self,
664 caller_instance: &str,
665 ) -> Result<(&ReplicatedLaneRoute, Option<CrossLaneDiagnostics>), RuntimeFailure> {
666 let binding = singular_binding::<C>(&self.plan, caller_instance)?;
667 let consumer = self.plan.module_instance(caller_instance).ok_or_else(|| {
668 RuntimeFailure::InvalidResolvedPlan {
669 detail: format!("binding consumer `{caller_instance}` is absent from the Plan"),
670 }
671 })?;
672 let provider = self
673 .plan
674 .module_instance(binding.provider_instance())
675 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
676 detail: format!(
677 "binding provider `{}` is absent from the Plan",
678 binding.provider_instance()
679 ),
680 })?;
681 let lane =
682 self.lanes
683 .get(provider.execution_lane())
684 .ok_or_else(|| RuntimeFailure::Internal {
685 detail: format!(
686 "Execution Lane `{}` is unavailable",
687 provider.execution_lane()
688 ),
689 })?;
690 let diagnostics = (consumer.execution_lane() != provider.execution_lane()).then(|| {
691 (
692 Arc::clone(&self.diagnostics),
693 consumer.execution_lane().clone(),
694 binding.provider_instance().to_owned(),
695 )
696 });
697 Ok((lane, diagnostics))
698 }
699
700 pub async fn invoke<C: RequestCapability>(
702 &self,
703 caller_instance: &str,
704 operation: &str,
705 request: C::Request,
706 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
707 where
708 C::Request: Send,
709 C::Response: Send,
710 C::DomainError: Send,
711 {
712 self.invoke_with_options::<C>(
713 caller_instance,
714 operation,
715 request,
716 LaneInvocationOptions::new(),
717 )
718 .await
719 }
720
721 pub async fn invoke_with_options<C: RequestCapability>(
726 &self,
727 caller_instance: &str,
728 operation: &str,
729 request: C::Request,
730 options: LaneInvocationOptions,
731 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
732 where
733 C::Request: Send,
734 C::Response: Send,
735 C::DomainError: Send,
736 {
737 self.ensure_running()?;
738 let (lane, cross_lane_diagnostics) = self.resolve_request_lane::<C>(caller_instance)?;
741 let caller_instance = caller_instance.to_owned();
742 let operation = operation.to_owned();
743 let deadline = options
744 .timeout
745 .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
746 let admission_timeout = options.timeout;
747 let admission_cancellation = options.cancellation.clone();
748 let controlled = deadline.is_some() || admission_cancellation.is_some();
749 let (completed, completion) = oneshot::channel();
750 let (started, start) = if controlled {
751 let (started, start) = oneshot::channel();
752 (Some(started), Some(start))
753 } else {
754 (None, None)
755 };
756 let task = Box::new(move |lane: LaneRuntime| {
757 if let Some((diagnostics, caller_lane, provider_instance)) = cross_lane_diagnostics {
758 diagnostics.record_invocation(&caller_lane, &caller_instance, &provider_instance);
759 }
760 tokio::task::spawn_local(async move {
761 let handle = match lane.request_handle::<C>(&caller_instance) {
762 Ok(handle) => handle,
763 Err(error) => {
764 let _ = completed.send(Err(error));
765 return;
766 }
767 };
768 let cancellation = CancellationToken::new();
769 let external_cancellation = options.cancellation;
770 if external_cancellation
771 .as_ref()
772 .is_some_and(LaneCancellationToken::is_cancelled)
773 {
774 cancellation.cancel();
775 }
776 let invocation = if deadline.is_some() || external_cancellation.is_some() {
777 let context = lane.app.invocation_context(deadline, cancellation.clone());
778 if let Some(started) = started {
779 let _ = started.send(());
780 }
781 Either::Left(handle.invoke_with_context(&operation, context, request))
782 } else {
783 Either::Right(handle.invoke(&operation, request))
784 };
785 tokio::pin!(invocation);
786 let result = if let Some(external_cancellation) = external_cancellation {
787 tokio::select! {
788 result = &mut invocation => result,
789 () = external_cancellation.cancelled() => {
790 cancellation.cancel();
791 invocation.await
792 }
793 }
794 } else {
795 invocation.await
796 };
797 let _ = completed.send(result);
798 });
799 });
800 if let Some(start) = start {
801 return admission::dispatch_controlled(
802 &lane.id,
803 &lane.commands,
804 task,
805 start,
806 completion,
807 admission_timeout,
808 admission_cancellation,
809 )
810 .await?;
811 }
812 lane.commands
813 .send(task)
814 .await
815 .map_err(|_| RuntimeFailure::Internal {
816 detail: format!("Execution Lane `{}` is unavailable", lane.id),
817 })?;
818 completion.await.map_err(|_| RuntimeFailure::Internal {
819 detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
820 })?
821 }
822}
823
824impl ReplicatedNativeApp {
825 pub async fn invoke<C: RequestCapability>(
827 &self,
828 caller_instance: &str,
829 operation: &str,
830 request: C::Request,
831 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
832 where
833 C::Request: Send,
834 C::Response: Send,
835 C::DomainError: Send,
836 {
837 self.route()
838 .invoke::<C>(caller_instance, operation, request)
839 .await
840 }
841
842 pub async fn invoke_with_options<C: RequestCapability>(
844 &self,
845 caller_instance: &str,
846 operation: &str,
847 request: C::Request,
848 options: LaneInvocationOptions,
849 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
850 where
851 C::Request: Send,
852 C::Response: Send,
853 C::DomainError: Send,
854 {
855 self.route()
856 .invoke_with_options::<C>(caller_instance, operation, request, options)
857 .await
858 }
859
860 pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
862 self.terminal.begin_shutdown();
863 let mut completions = Vec::new();
864 let mut threads = Vec::new();
865 let mut first_error = self.terminal.failure();
866 for (_, lane) in self.lanes {
867 let LaneHandle {
868 id,
869 commands,
870 shutdown,
871 thread,
872 } = lane;
873 let (completed, completion) = oneshot::channel();
874 if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
875 completions.push((id.clone(), completion));
876 } else if first_error.is_none() {
877 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
878 lane: id.to_string(),
879 });
880 }
881 drop(commands);
882 threads.push((id, thread));
883 }
884
885 for (lane, completion) in completions {
886 match completion.await {
887 Ok(ShutdownOutcome::Clean) => {}
888 Ok(outcome) if first_error.is_none() => {
889 first_error = Some(ReplicatedRunnerError::LaneShutdown {
890 lane: lane.to_string(),
891 outcome,
892 });
893 }
894 Err(_) if first_error.is_none() => {
895 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
896 lane: lane.to_string(),
897 });
898 }
899 _ => {}
900 }
901 }
902 for (lane, thread) in threads {
903 if thread.join().is_err() && first_error.is_none() {
904 first_error = Some(ReplicatedRunnerError::LanePanicked {
905 lane: lane.to_string(),
906 });
907 }
908 }
909 match first_error {
910 Some(error) => Err(error),
911 None => Ok(()),
912 }
913 }
914}
915
916fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
917 let mut threads = Vec::new();
918 for (_, lane) in lanes {
919 let (completed, _) = oneshot::channel();
920 let _ = lane.shutdown.send(LaneShutdown {
921 timeout: Duration::from_secs(1),
922 completed,
923 });
924 threads.push(lane.thread);
925 }
926 for thread in threads {
927 let _ = thread.join();
928 }
929}
930
931fn singular_binding<'a, C: RequestCapability>(
932 plan: &'a ResolvedAppPlan,
933 caller_instance: &str,
934) -> Result<&'a CapabilityBinding, RuntimeFailure> {
935 let mut bindings = plan.capability_bindings().iter().filter(|binding| {
936 binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
937 });
938 let Some(binding) = bindings.next() else {
939 return Err(RuntimeFailure::Unavailable { capability: C::ID });
940 };
941 let providers = 1 + bindings.count();
942 if providers == 1 {
943 Ok(binding)
944 } else {
945 Err(RuntimeFailure::AmbiguousBinding {
946 capability: C::ID,
947 providers,
948 })
949 }
950}
951
952#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
953fn run_lane<F>(
954 lane: ExecutionLaneId,
955 plan: ResolvedAppPlan,
956 mut commands: mpsc::Receiver<LaneTask>,
957 mut shutdown: oneshot::Receiver<LaneShutdown>,
958 started: std_mpsc::SyncSender<Result<(), String>>,
959 adapters: Arc<F>,
960 proxy_adapter: LaneProxyAdapter,
961 diagnostics: Arc<LaneDiagnosticsState>,
962 terminal: Arc<ReplicatedTerminalState>,
963 epoch: Instant,
964) where
965 F: Fn(&ExecutionLaneId) -> Result<ExecutionAdapterCatalog, String> + Send + Sync + 'static,
966{
967 let runtime = match tokio::runtime::Builder::new_current_thread()
968 .enable_all()
969 .build()
970 {
971 Ok(runtime) => runtime,
972 Err(error) => {
973 let _ = started.send(Err(error.to_string()));
974 return;
975 }
976 };
977 let local = tokio::task::LocalSet::new();
978 local.block_on(&runtime, async move {
979 let cpu_started = ThreadTime::now();
980 let catalog = match adapters(&lane) {
981 Ok(catalog) => match catalog.with_adapter(proxy_adapter) {
982 Ok(catalog) => catalog,
983 Err(error) => {
984 let _ = started.send(Err(error.to_string()));
985 return;
986 }
987 },
988 Err(detail) => {
989 let _ = started.send(Err(detail));
990 return;
991 }
992 };
993 let driver = TokioDriver::with_epoch(epoch);
994 let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
995 LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
996 ));
997 let start = lenso_kernel::Kernel::start_with_diagnostics(
998 plan,
999 driver,
1000 catalog,
1001 runtime_diagnostics,
1002 );
1003 tokio::pin!(start);
1004 let app = match tokio::select! {
1005 result = &mut start => Some(result),
1006 _ = &mut shutdown => None,
1007 } {
1008 Some(Ok(app)) => app,
1009 Some(Err(error)) => {
1010 let _ = started.send(Err(format!("{error:?}")));
1011 return;
1012 }
1013 None => {
1014 let _ = started.send(Err("lane startup cancelled before Ready".to_owned()));
1015 return;
1016 }
1017 };
1018 let lane_runtime = LaneRuntime::new(app.clone());
1019 let _ = started.send(Ok(()));
1020 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
1021 let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
1022 let terminal_monitor = tokio::task::spawn_local(monitor_lane_failure(
1023 lane.clone(),
1024 app.clone(),
1025 Arc::clone(&terminal),
1026 ));
1027 let terminal_failure = terminal.wait();
1028 tokio::pin!(terminal_failure);
1029
1030 loop {
1031 tokio::select! {
1032 biased;
1033 shutdown = &mut shutdown => {
1034 terminal_monitor.abort();
1035 match shutdown {
1036 Ok(LaneShutdown { timeout, completed }) => {
1037 let outcome = app.shutdown(timeout).await;
1038 let _ = completed.send(outcome);
1039 }
1040 Err(_) => {
1041 let _ = app.shutdown(Duration::from_secs(1)).await;
1042 }
1043 }
1044 break;
1045 }
1046 _ = &mut terminal_failure => {
1047 terminal_monitor.abort();
1048 let _ = app.shutdown(Duration::from_secs(1)).await;
1049 break;
1050 },
1051 command = commands.recv() => if let Some(task) = command {
1052 task(lane_runtime.clone());
1053 } else {
1054 terminal_monitor.abort();
1055 if !terminal.is_stopping() {
1056 terminal.fail(ReplicatedRunnerError::LaneUnavailable {
1057 lane: lane.to_string(),
1058 });
1059 }
1060 let _ = app.shutdown(Duration::from_secs(1)).await;
1061 break;
1062 },
1063 _ = sample_interval.tick() => {
1064 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
1065 }
1066 }
1067 }
1068 });
1069}
1070
1071async fn monitor_lane_failure(
1072 lane: ExecutionLaneId,
1073 app: NativeApp,
1074 terminal: Arc<ReplicatedTerminalState>,
1075) {
1076 let mut failure_interval = tokio::time::interval(Duration::from_millis(10));
1077 loop {
1078 failure_interval.tick().await;
1079 if let Some(error) = app.terminal_failure() {
1080 terminal.fail(ReplicatedRunnerError::LaneRuntimeFailure {
1081 lane: lane.to_string(),
1082 error,
1083 });
1084 return;
1085 }
1086 if terminal.is_failed() {
1087 return;
1088 }
1089 }
1090}
1091
1092#[cfg(test)]
1093mod tests {
1094 use std::time::Duration;
1095
1096 use lenso_app_plan::{AppComposition, ExecutionLaneId, ExecutionLanePlan};
1097 use lenso_kernel::ExecutionAdapterCatalog;
1098
1099 use super::{ReplicatedNativeApp, ReplicatedRunnerError};
1100
1101 #[tokio::test(flavor = "current_thread")]
1102 async fn one_lane_panic_makes_the_replicated_app_terminal_and_stops_its_peers() {
1103 let plan = AppComposition::new(Vec::new(), Vec::new())
1104 .with_execution_lanes(vec![
1105 ExecutionLanePlan::new("lane-a"),
1106 ExecutionLanePlan::new("lane-b"),
1107 ])
1108 .resolve()
1109 .expect("the empty two-lane Plan should resolve");
1110 let app = ReplicatedNativeApp::start(plan, |_| ExecutionAdapterCatalog::new())
1111 .expect("both empty Kernel lanes should start");
1112 let peer_commands = app
1113 .lanes
1114 .get(&ExecutionLaneId::new("lane-b"))
1115 .expect("lane-b should exist")
1116 .commands
1117 .clone();
1118 let flooding =
1119 tokio::spawn(
1120 async move { while peer_commands.send(Box::new(|_| {})).await.is_ok() {} },
1121 );
1122 app.lanes
1123 .get(&ExecutionLaneId::new("lane-a"))
1124 .expect("lane-a should exist")
1125 .commands
1126 .send(Box::new(|_| panic!("injected lane panic")))
1127 .await
1128 .expect("lane-a should accept the injected task");
1129
1130 let failure = tokio::time::timeout(Duration::from_secs(1), app.wait_for_terminal())
1131 .await
1132 .expect("the lane panic should become terminal promptly");
1133 assert_eq!(
1134 failure,
1135 ReplicatedRunnerError::LanePanicked {
1136 lane: "lane-a".to_owned(),
1137 }
1138 );
1139 assert!(app.is_failed());
1140 assert_eq!(app.terminal_failure(), Some(failure.clone()));
1141 assert_eq!(
1142 tokio::time::timeout(Duration::from_secs(1), app.shutdown(Duration::from_secs(1)))
1143 .await
1144 .expect("a saturated peer lane should still observe terminal failure"),
1145 Err(failure)
1146 );
1147 flooding
1148 .await
1149 .expect("the peer command producer should stop when the lane closes");
1150 }
1151}