Skip to main content

lenso_runner/
replicated.rs

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 diagnostics;
26mod error;
27mod interaction_transfer;
28mod projection;
29mod terminal;
30mod transfer;
31
32pub use diagnostics::LaneDiagnosticsSnapshot;
33use diagnostics::{LaneDiagnosticsState, LaneInvocationProbe};
34pub use error::ReplicatedRunnerError;
35use interaction_transfer::CrossLaneInteractionCatalog;
36use projection::{LaneProxyAdapter, project_lane};
37use terminal::ReplicatedTerminalState;
38pub use transfer::CrossLaneRequestCatalog;
39
40const LANE_PROXY_EXECUTION_CLASS: &str = "lenso.native-lane-proxy@1";
41
42/// Placement-independent controls applied by the provider lane's Runtime Driver.
43#[derive(Clone, Debug, Default)]
44pub struct LaneInvocationOptions {
45    timeout: Option<Duration>,
46    cancellation: Option<LaneCancellationToken>,
47}
48
49impl LaneInvocationOptions {
50    /// Creates an invocation without a deadline.
51    pub const fn new() -> Self {
52        Self {
53            timeout: None,
54            cancellation: None,
55        }
56    }
57
58    /// Applies a provider-Driver-relative deadline.
59    #[must_use]
60    pub const fn with_timeout(mut self, timeout: Duration) -> Self {
61        self.timeout = Some(timeout);
62        self
63    }
64
65    /// Propagates one caller-owned cooperative cancellation signal.
66    #[must_use]
67    pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
68        self.cancellation = Some(cancellation);
69        self
70    }
71}
72
73/// A thread-safe caller signal translated to a lane-local Kernel cancellation token.
74#[derive(Clone, Debug)]
75pub struct LaneCancellationToken {
76    cancelled: watch::Sender<bool>,
77}
78
79impl Default for LaneCancellationToken {
80    fn default() -> Self {
81        let (cancelled, _) = watch::channel(false);
82        Self { cancelled }
83    }
84}
85
86impl LaneCancellationToken {
87    /// Creates a signal that has not been cancelled.
88    pub fn new() -> Self {
89        Self::default()
90    }
91
92    /// Requests cooperative cancellation of attached invocations.
93    pub fn cancel(&self) {
94        self.cancelled.send_replace(true);
95    }
96
97    /// Returns whether cancellation was requested.
98    pub fn is_cancelled(&self) -> bool {
99        *self.cancelled.borrow()
100    }
101
102    async fn cancelled(&self) {
103        let mut cancelled = self.cancelled.subscribe();
104        loop {
105            if *cancelled.borrow_and_update() {
106                return;
107            }
108            if cancelled.changed().await.is_err() {
109                return;
110            }
111        }
112    }
113}
114
115type LaneTask = Box<dyn FnOnce(LaneRuntime) + Send + 'static>;
116type LaneSender = mpsc::Sender<LaneTask>;
117type LaneRoute = mpsc::WeakSender<LaneTask>;
118
119struct LaneShutdown {
120    timeout: Duration,
121    completed: oneshot::Sender<ShutdownOutcome>,
122}
123
124struct LaneHandle {
125    id: ExecutionLaneId,
126    commands: LaneSender,
127    shutdown: oneshot::Sender<LaneShutdown>,
128    thread: thread::JoinHandle<()>,
129}
130
131type TypedRequestHandles = HashMap<String, Box<dyn Any>>;
132type TypedStreamSessions = HashMap<(TypeId, u64), Box<dyn Any>>;
133
134#[derive(Clone)]
135struct LaneRuntime {
136    app: NativeApp,
137    request_handles: Rc<RefCell<HashMap<TypeId, TypedRequestHandles>>>,
138    stream_sessions: Rc<RefCell<TypedStreamSessions>>,
139}
140
141impl LaneRuntime {
142    fn new(app: NativeApp) -> Self {
143        Self {
144            app,
145            request_handles: Rc::new(RefCell::new(HashMap::new())),
146            stream_sessions: Rc::new(RefCell::new(HashMap::new())),
147        }
148    }
149
150    fn request_handle<C: RequestCapability>(
151        &self,
152        caller_instance: &str,
153    ) -> Result<Rc<NativeRequestHandle<C>>, RuntimeFailure> {
154        let capability = TypeId::of::<C>();
155        if let Some(handle) = self
156            .request_handles
157            .borrow()
158            .get(&capability)
159            .and_then(|handles| handles.get(caller_instance))
160            .and_then(|handle| handle.downcast_ref::<Rc<NativeRequestHandle<C>>>())
161        {
162            return Ok(handle.clone());
163        }
164        let handle = Rc::new(self.app.handle::<C>(caller_instance)?);
165        self.request_handles
166            .borrow_mut()
167            .entry(capability)
168            .or_default()
169            .insert(caller_instance.to_owned(), Box::new(handle.clone()));
170        Ok(handle)
171    }
172
173    fn stream_handle<C: StreamCapability>(
174        &self,
175        caller_instance: &str,
176        provider_instance: &str,
177    ) -> Result<NativeStreamHandle<C>, RuntimeFailure> {
178        let dependencies = self.app.dependencies(caller_instance)?;
179        dependencies
180            .bindings()
181            .iter()
182            .find(|binding| {
183                binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
184            })
185            .and_then(lenso_kernel::ModuleDependency::stream_handle)
186            .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
187            .typed::<C>()
188    }
189
190    fn event_handle<C: EventCapability>(
191        &self,
192        caller_instance: &str,
193        provider_instance: &str,
194    ) -> Result<NativeEventHandle<C>, RuntimeFailure> {
195        let dependencies = self.app.dependencies(caller_instance)?;
196        dependencies
197            .bindings()
198            .iter()
199            .find(|binding| {
200                binding.capability_id() == C::ID && binding.provider_instance() == provider_instance
201            })
202            .and_then(lenso_kernel::ModuleDependency::event_handle)
203            .ok_or(RuntimeFailure::Unavailable { capability: C::ID })?
204            .typed::<C>()
205    }
206
207    fn insert_stream<C: StreamCapability>(&self, session_id: u64, stream: NativeStream<C>) {
208        self.stream_sessions
209            .borrow_mut()
210            .insert((TypeId::of::<C>(), session_id), Box::new(Rc::new(stream)));
211    }
212
213    fn stream<C: StreamCapability>(
214        &self,
215        session_id: u64,
216    ) -> Result<Rc<NativeStream<C>>, RuntimeFailure> {
217        self.stream_sessions
218            .borrow()
219            .get(&(TypeId::of::<C>(), session_id))
220            .and_then(|stream| stream.downcast_ref::<Rc<NativeStream<C>>>())
221            .cloned()
222            .ok_or(RuntimeFailure::Unavailable { capability: C::ID })
223    }
224
225    fn remove_stream<C: StreamCapability>(&self, session_id: u64) {
226        self.stream_sessions
227            .borrow_mut()
228            .remove(&(TypeId::of::<C>(), session_id));
229    }
230}
231
232/// Generated native values registered for zero-serialization transfer between Kernel lanes.
233#[derive(Clone, Debug, Default)]
234pub struct CrossLaneTransferCatalog {
235    requests: CrossLaneRequestCatalog,
236    interactions: CrossLaneInteractionCatalog,
237}
238
239impl CrossLaneTransferCatalog {
240    /// Creates an empty catalog for a Plan whose bindings remain on one lane.
241    pub fn new() -> Self {
242        Self::default()
243    }
244
245    /// Registers one generated request Capability whose values are `Send`.
246    #[must_use]
247    pub fn with_request<C>(mut self, operations: &'static [&'static str]) -> Self
248    where
249        C: RequestCapability,
250        C::Request: Send,
251        C::Response: Send,
252        C::DomainError: Send,
253    {
254        self.requests = self.requests.with_request::<C>(operations);
255        self
256    }
257
258    /// Registers one generated stream Capability whose values are `Send`.
259    #[must_use]
260    pub fn with_stream<C>(mut self, operations: &'static [&'static str]) -> Self
261    where
262        C: StreamCapability,
263        C::OpenRequest: Send,
264        C::Message: Send,
265        C::DomainError: Send,
266    {
267        self.interactions = self.interactions.with_stream::<C>(operations);
268        self
269    }
270
271    /// Registers one generated ephemeral Event Capability whose values are `Send`.
272    #[must_use]
273    pub fn with_event<C>(mut self, operations: &'static [&'static str]) -> Self
274    where
275        C: EventCapability,
276        C::Event: Send,
277    {
278        self.interactions = self.interactions.with_event::<C>(operations);
279        self
280    }
281
282    fn validate_plan(&self, plan: &ResolvedAppPlan) -> Result<(), ReplicatedRunnerError> {
283        self.requests.validate_plan(plan)?;
284        self.interactions.validate_plan(plan)
285    }
286}
287
288impl From<CrossLaneRequestCatalog> for CrossLaneTransferCatalog {
289    fn from(requests: CrossLaneRequestCatalog) -> Self {
290        Self {
291            requests,
292            interactions: CrossLaneInteractionCatalog::default(),
293        }
294    }
295}
296
297/// One native App executed as a fixed set of Plan-declared single-owner Kernel lanes.
298pub struct ReplicatedNativeApp {
299    plan: Arc<ResolvedAppPlan>,
300    lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
301    diagnostics: Arc<LaneDiagnosticsState>,
302    terminal: Arc<ReplicatedTerminalState>,
303    epoch: Instant,
304}
305
306impl fmt::Debug for ReplicatedNativeApp {
307    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
308        formatter
309            .debug_struct("ReplicatedNativeApp")
310            .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
311            .finish_non_exhaustive()
312    }
313}
314
315impl ReplicatedNativeApp {
316    fn ensure_running(&self) -> Result<(), RuntimeFailure> {
317        if let Some(failure) = self.terminal.failure() {
318            return Err(RuntimeFailure::Internal {
319                detail: failure.to_string(),
320            });
321        }
322        Ok(())
323    }
324
325    /// Starts one unmodified Kernel replica per declared Execution Lane.
326    pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
327    where
328        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
329    {
330        Self::start_with_transfer_catalog(plan, adapters, CrossLaneTransferCatalog::new())
331    }
332
333    /// Starts replicated lanes with generated request types allowed to cross them.
334    #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
335    pub fn start_with_transfers<F>(
336        plan: ResolvedAppPlan,
337        adapters: F,
338        transfers: CrossLaneRequestCatalog,
339    ) -> Result<Self, ReplicatedRunnerError>
340    where
341        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
342    {
343        Self::start_with_transfer_catalog(plan, adapters, transfers.into())
344    }
345
346    /// Starts replicated lanes with generated values allowed to cross them without serialization.
347    #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
348    pub fn start_with_transfer_catalog<F>(
349        plan: ResolvedAppPlan,
350        adapters: F,
351        transfers: CrossLaneTransferCatalog,
352    ) -> Result<Self, ReplicatedRunnerError>
353    where
354        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
355    {
356        plan.validate()
357            .map_err(|error| ReplicatedRunnerError::InvalidPlan {
358                detail: error.to_string(),
359            })?;
360        transfers.validate_plan(&plan)?;
361        let plan = Arc::new(plan);
362        let adapters = Arc::new(adapters);
363        let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
364        let terminal = Arc::new(ReplicatedTerminalState::default());
365        let epoch = Instant::now();
366        let mut receivers = BTreeMap::new();
367        let senders = plan
368            .execution_lanes()
369            .iter()
370            .map(|lane| {
371                let (sender, receiver) = mpsc::channel(64);
372                receivers.insert(lane.id().clone(), receiver);
373                (lane.id().clone(), sender)
374            })
375            .collect::<BTreeMap<_, _>>();
376        let routes = Arc::new(
377            senders
378                .iter()
379                .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
380                .collect::<BTreeMap<_, _>>(),
381        );
382        let projected = plan
383            .execution_lanes()
384            .iter()
385            .map(|lane| {
386                project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
387            })
388            .collect::<Result<Vec<_>, _>>()?;
389        let mut lanes = BTreeMap::new();
390        let mut startups = Vec::new();
391
392        for (lane_id, lane_plan) in projected {
393            let commands = senders
394                .get(&lane_id)
395                .expect("every declared lane has a command route")
396                .clone();
397            let receiver = receivers
398                .remove(&lane_id)
399                .expect("every declared lane has one command receiver");
400            let (shutdown, shutdown_request) = oneshot::channel();
401            let (started, startup) = std_mpsc::sync_channel(1);
402            let lane_adapters = Arc::clone(&adapters);
403            let lane_diagnostics = Arc::clone(&diagnostics);
404            let lane_terminal = Arc::clone(&terminal);
405            let proxy_adapter = LaneProxyAdapter::new(
406                Arc::clone(&plan),
407                transfers.clone(),
408                Arc::clone(&routes),
409                epoch,
410            );
411            let thread_lane = lane_id.clone();
412            let lane_thread = match thread::Builder::new()
413                .name(format!("lenso-lane-{}", lane_id.as_str()))
414                .spawn(move || {
415                    let reported_lane = thread_lane.clone();
416                    let result = catch_unwind(AssertUnwindSafe(|| {
417                        run_lane(
418                            thread_lane,
419                            lane_plan,
420                            receiver,
421                            shutdown_request,
422                            started,
423                            lane_adapters,
424                            proxy_adapter,
425                            lane_diagnostics,
426                            Arc::clone(&lane_terminal),
427                            epoch,
428                        );
429                    }));
430                    if result.is_err() {
431                        lane_terminal.fail(ReplicatedRunnerError::LanePanicked {
432                            lane: reported_lane.to_string(),
433                        });
434                    }
435                }) {
436                Ok(thread) => thread,
437                Err(error) => {
438                    drop(receivers);
439                    drop(routes);
440                    drop(senders);
441                    terminal.begin_shutdown();
442                    stop_lanes(lanes);
443                    return Err(ReplicatedRunnerError::LaneStartup {
444                        lane: lane_id.to_string(),
445                        detail: error.to_string(),
446                    });
447                }
448            };
449            startups.push((lane_id.clone(), startup));
450            lanes.insert(
451                lane_id.clone(),
452                LaneHandle {
453                    id: lane_id,
454                    commands,
455                    shutdown,
456                    thread: lane_thread,
457                },
458            );
459        }
460
461        for (lane, startup) in startups {
462            match startup.recv() {
463                Ok(Ok(())) => {}
464                Ok(Err(detail)) => {
465                    drop(routes);
466                    drop(senders);
467                    terminal.begin_shutdown();
468                    stop_lanes(lanes);
469                    return Err(ReplicatedRunnerError::LaneStartup {
470                        lane: lane.to_string(),
471                        detail,
472                    });
473                }
474                Err(_) => {
475                    drop(routes);
476                    drop(senders);
477                    let failure = terminal.failure().unwrap_or_else(|| {
478                        ReplicatedRunnerError::LaneUnavailable {
479                            lane: lane.to_string(),
480                        }
481                    });
482                    terminal.begin_shutdown();
483                    stop_lanes(lanes);
484                    return Err(failure);
485                }
486            }
487        }
488
489        Ok(Self {
490            plan,
491            lanes,
492            diagnostics,
493            terminal,
494            epoch,
495        })
496    }
497
498    /// Returns the fixed number of Kernel replicas started from the Plan.
499    pub fn lane_count(&self) -> usize {
500        self.lanes.len()
501    }
502
503    /// Returns structural evidence for placement decisions without exposing payloads.
504    pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
505        self.diagnostics.snapshot()
506    }
507
508    /// Returns whether any Kernel lane reached a terminal failure.
509    pub fn is_failed(&self) -> bool {
510        self.terminal.is_failed()
511    }
512
513    /// Returns the first App-terminal lane failure, when one has occurred.
514    pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
515        self.terminal.failure()
516    }
517
518    /// Waits until one lane makes the replicated App terminal.
519    pub async fn wait_for_terminal(&self) -> ReplicatedRunnerError {
520        self.terminal.wait().await
521    }
522
523    /// Invokes one generated request Capability on its Plan-placed provider lane.
524    pub async fn invoke<C: RequestCapability>(
525        &self,
526        caller_instance: &str,
527        operation: &str,
528        request: C::Request,
529    ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
530    where
531        C::Request: Send,
532        C::Response: Send,
533        C::DomainError: Send,
534    {
535        self.invoke_with_options::<C>(
536            caller_instance,
537            operation,
538            request,
539            LaneInvocationOptions::new(),
540        )
541        .await
542    }
543
544    /// Invokes one generated request with Driver-relative controls on its provider lane.
545    pub async fn invoke_with_options<C: RequestCapability>(
546        &self,
547        caller_instance: &str,
548        operation: &str,
549        request: C::Request,
550        options: LaneInvocationOptions,
551    ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
552    where
553        C::Request: Send,
554        C::Response: Send,
555        C::DomainError: Send,
556    {
557        self.ensure_running()?;
558        let binding = singular_binding::<C>(&self.plan, caller_instance)?;
559        let consumer = self.plan.module_instance(caller_instance).ok_or_else(|| {
560            RuntimeFailure::InvalidResolvedPlan {
561                detail: format!("binding consumer `{caller_instance}` is absent from the Plan"),
562            }
563        })?;
564        let provider = self
565            .plan
566            .module_instance(binding.provider_instance())
567            .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
568                detail: format!(
569                    "binding provider `{}` is absent from the Plan",
570                    binding.provider_instance()
571                ),
572            })?;
573        // An invocation entering through the Runner can start on the resolved provider owner.
574        // Calls originating inside a Module still use the projected cross-lane transfer endpoint.
575        let lane =
576            self.lanes
577                .get(provider.execution_lane())
578                .ok_or_else(|| RuntimeFailure::Internal {
579                    detail: format!(
580                        "Execution Lane `{}` is unavailable",
581                        provider.execution_lane()
582                    ),
583                })?;
584        let cross_lane_diagnostics =
585            (consumer.execution_lane() != provider.execution_lane()).then(|| {
586                (
587                    Arc::clone(&self.diagnostics),
588                    consumer.execution_lane().clone(),
589                    binding.provider_instance().to_owned(),
590                )
591            });
592        let caller_instance = caller_instance.to_owned();
593        let operation = operation.to_owned();
594        let deadline = options
595            .timeout
596            .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
597        let (completed, completion) = oneshot::channel();
598        lane.commands
599            .send(Box::new(move |lane| {
600                if let Some((diagnostics, caller_lane, provider_instance)) = cross_lane_diagnostics
601                {
602                    diagnostics.record_invocation(
603                        &caller_lane,
604                        &caller_instance,
605                        &provider_instance,
606                    );
607                }
608                tokio::task::spawn_local(async move {
609                    let handle = match lane.request_handle::<C>(&caller_instance) {
610                        Ok(handle) => handle,
611                        Err(error) => {
612                            let _ = completed.send(Err(error));
613                            return;
614                        }
615                    };
616                    let cancellation = CancellationToken::new();
617                    let external_cancellation = options.cancellation;
618                    if external_cancellation
619                        .as_ref()
620                        .is_some_and(LaneCancellationToken::is_cancelled)
621                    {
622                        cancellation.cancel();
623                    }
624                    let invocation = if deadline.is_some() || external_cancellation.is_some() {
625                        let context = lane.app.invocation_context(deadline, cancellation.clone());
626                        Either::Left(handle.invoke_with_context(&operation, context, request))
627                    } else {
628                        Either::Right(handle.invoke(&operation, request))
629                    };
630                    tokio::pin!(invocation);
631                    let result = if let Some(external_cancellation) = external_cancellation {
632                        tokio::select! {
633                            result = &mut invocation => result,
634                            () = external_cancellation.cancelled() => {
635                                cancellation.cancel();
636                                invocation.await
637                            }
638                        }
639                    } else {
640                        invocation.await
641                    };
642                    let _ = completed.send(result);
643                });
644            }))
645            .await
646            .map_err(|_| RuntimeFailure::Internal {
647                detail: format!("Execution Lane `{}` is unavailable", lane.id),
648            })?;
649        completion.await.map_err(|_| RuntimeFailure::Internal {
650            detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
651        })?
652    }
653
654    /// Stops every Kernel replica with the same bounded shutdown timeout.
655    pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
656        self.terminal.begin_shutdown();
657        let mut completions = Vec::new();
658        let mut threads = Vec::new();
659        let mut first_error = self.terminal.failure();
660        for (_, lane) in self.lanes {
661            let LaneHandle {
662                id,
663                commands,
664                shutdown,
665                thread,
666            } = lane;
667            let (completed, completion) = oneshot::channel();
668            if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
669                completions.push((id.clone(), completion));
670            } else if first_error.is_none() {
671                first_error = Some(ReplicatedRunnerError::LaneUnavailable {
672                    lane: id.to_string(),
673                });
674            }
675            drop(commands);
676            threads.push((id, thread));
677        }
678
679        for (lane, completion) in completions {
680            match completion.await {
681                Ok(ShutdownOutcome::Clean) => {}
682                Ok(outcome) if first_error.is_none() => {
683                    first_error = Some(ReplicatedRunnerError::LaneShutdown {
684                        lane: lane.to_string(),
685                        outcome,
686                    });
687                }
688                Err(_) if first_error.is_none() => {
689                    first_error = Some(ReplicatedRunnerError::LaneUnavailable {
690                        lane: lane.to_string(),
691                    });
692                }
693                _ => {}
694            }
695        }
696        for (lane, thread) in threads {
697            if thread.join().is_err() && first_error.is_none() {
698                first_error = Some(ReplicatedRunnerError::LanePanicked {
699                    lane: lane.to_string(),
700                });
701            }
702        }
703        match first_error {
704            Some(error) => Err(error),
705            None => Ok(()),
706        }
707    }
708}
709
710fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
711    let mut threads = Vec::new();
712    for (_, lane) in lanes {
713        let (completed, _) = oneshot::channel();
714        let _ = lane.shutdown.send(LaneShutdown {
715            timeout: Duration::from_secs(1),
716            completed,
717        });
718        threads.push(lane.thread);
719    }
720    for thread in threads {
721        let _ = thread.join();
722    }
723}
724
725fn singular_binding<'a, C: RequestCapability>(
726    plan: &'a ResolvedAppPlan,
727    caller_instance: &str,
728) -> Result<&'a CapabilityBinding, RuntimeFailure> {
729    let mut bindings = plan.capability_bindings().iter().filter(|binding| {
730        binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
731    });
732    let Some(binding) = bindings.next() else {
733        return Err(RuntimeFailure::Unavailable { capability: C::ID });
734    };
735    let providers = 1 + bindings.count();
736    if providers == 1 {
737        Ok(binding)
738    } else {
739        Err(RuntimeFailure::AmbiguousBinding {
740            capability: C::ID,
741            providers,
742        })
743    }
744}
745
746#[allow(clippy::too_many_arguments)]
747fn run_lane<F>(
748    lane: ExecutionLaneId,
749    plan: ResolvedAppPlan,
750    mut commands: mpsc::Receiver<LaneTask>,
751    mut shutdown: oneshot::Receiver<LaneShutdown>,
752    started: std_mpsc::SyncSender<Result<(), String>>,
753    adapters: Arc<F>,
754    proxy_adapter: LaneProxyAdapter,
755    diagnostics: Arc<LaneDiagnosticsState>,
756    terminal: Arc<ReplicatedTerminalState>,
757    epoch: Instant,
758) where
759    F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
760{
761    let runtime = match tokio::runtime::Builder::new_current_thread()
762        .enable_all()
763        .build()
764    {
765        Ok(runtime) => runtime,
766        Err(error) => {
767            let _ = started.send(Err(error.to_string()));
768            return;
769        }
770    };
771    let local = tokio::task::LocalSet::new();
772    local.block_on(&runtime, async move {
773        let cpu_started = ThreadTime::now();
774        let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
775            Ok(catalog) => catalog,
776            Err(error) => {
777                let _ = started.send(Err(error.to_string()));
778                return;
779            }
780        };
781        let driver = TokioDriver::with_epoch(epoch);
782        let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
783            LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
784        ));
785        let app = match lenso_kernel::Kernel::start_with_diagnostics(
786            plan,
787            driver,
788            catalog,
789            runtime_diagnostics,
790        )
791        .await
792        {
793            Ok(app) => app,
794            Err(error) => {
795                let _ = started.send(Err(format!("{error:?}")));
796                return;
797            }
798        };
799        let lane_runtime = LaneRuntime::new(app.clone());
800        let _ = started.send(Ok(()));
801        diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
802        let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
803        let terminal_monitor = tokio::task::spawn_local(monitor_lane_failure(
804            lane.clone(),
805            app.clone(),
806            Arc::clone(&terminal),
807        ));
808        let terminal_failure = terminal.wait();
809        tokio::pin!(terminal_failure);
810
811        loop {
812            tokio::select! {
813                biased;
814                shutdown = &mut shutdown => {
815                    terminal_monitor.abort();
816                    match shutdown {
817                        Ok(LaneShutdown { timeout, completed }) => {
818                            let outcome = app.shutdown(timeout).await;
819                            let _ = completed.send(outcome);
820                        }
821                        Err(_) => {
822                            let _ = app.shutdown(Duration::from_secs(1)).await;
823                        }
824                    }
825                    break;
826                }
827                _ = &mut terminal_failure => {
828                    terminal_monitor.abort();
829                    let _ = app.shutdown(Duration::from_secs(1)).await;
830                    break;
831                },
832                command = commands.recv() => if let Some(task) = command {
833                    task(lane_runtime.clone());
834                } else {
835                    terminal_monitor.abort();
836                    if !terminal.is_stopping() {
837                        terminal.fail(ReplicatedRunnerError::LaneUnavailable {
838                            lane: lane.to_string(),
839                        });
840                    }
841                    let _ = app.shutdown(Duration::from_secs(1)).await;
842                    break;
843                },
844                _ = sample_interval.tick() => {
845                    diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
846                }
847            }
848        }
849    });
850}
851
852async fn monitor_lane_failure(
853    lane: ExecutionLaneId,
854    app: NativeApp,
855    terminal: Arc<ReplicatedTerminalState>,
856) {
857    let mut failure_interval = tokio::time::interval(Duration::from_millis(10));
858    loop {
859        failure_interval.tick().await;
860        if let Some(error) = app.terminal_failure() {
861            terminal.fail(ReplicatedRunnerError::LaneRuntimeFailure {
862                lane: lane.to_string(),
863                error,
864            });
865            return;
866        }
867        if terminal.is_failed() {
868            return;
869        }
870    }
871}
872
873#[cfg(test)]
874mod tests {
875    use std::time::Duration;
876
877    use lenso_app_plan::{AppComposition, ExecutionLaneId, ExecutionLanePlan};
878    use lenso_kernel::ExecutionAdapterCatalog;
879
880    use super::{ReplicatedNativeApp, ReplicatedRunnerError};
881
882    #[tokio::test(flavor = "current_thread")]
883    async fn one_lane_panic_makes_the_replicated_app_terminal_and_stops_its_peers() {
884        let plan = AppComposition::new(Vec::new(), Vec::new())
885            .with_execution_lanes(vec![
886                ExecutionLanePlan::new("lane-a"),
887                ExecutionLanePlan::new("lane-b"),
888            ])
889            .resolve()
890            .expect("the empty two-lane Plan should resolve");
891        let app = ReplicatedNativeApp::start(plan, |_| ExecutionAdapterCatalog::new())
892            .expect("both empty Kernel lanes should start");
893        let peer_commands = app
894            .lanes
895            .get(&ExecutionLaneId::new("lane-b"))
896            .expect("lane-b should exist")
897            .commands
898            .clone();
899        let flooding =
900            tokio::spawn(
901                async move { while peer_commands.send(Box::new(|_| {})).await.is_ok() {} },
902            );
903        app.lanes
904            .get(&ExecutionLaneId::new("lane-a"))
905            .expect("lane-a should exist")
906            .commands
907            .send(Box::new(|_| panic!("injected lane panic")))
908            .await
909            .expect("lane-a should accept the injected task");
910
911        let failure = tokio::time::timeout(Duration::from_secs(1), app.wait_for_terminal())
912            .await
913            .expect("the lane panic should become terminal promptly");
914        assert_eq!(
915            failure,
916            ReplicatedRunnerError::LanePanicked {
917                lane: "lane-a".to_owned(),
918            }
919        );
920        assert!(app.is_failed());
921        assert_eq!(app.terminal_failure(), Some(failure.clone()));
922        assert_eq!(
923            tokio::time::timeout(Duration::from_secs(1), app.shutdown(Duration::from_secs(1)))
924                .await
925                .expect("a saturated peer lane should still observe terminal failure"),
926            Err(failure)
927        );
928        flooding
929            .await
930            .expect("the peer command producer should stop when the lane closes");
931    }
932}