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 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/// Placement-independent controls applied by the provider lane's Runtime Driver.
44#[derive(Clone, Debug, Default)]
45pub struct LaneInvocationOptions {
46    timeout: Option<Duration>,
47    cancellation: Option<LaneCancellationToken>,
48}
49
50impl LaneInvocationOptions {
51    /// Creates an invocation without a deadline.
52    pub const fn new() -> Self {
53        Self {
54            timeout: None,
55            cancellation: None,
56        }
57    }
58
59    /// Applies a provider-Driver-relative deadline.
60    #[must_use]
61    pub const fn with_timeout(mut self, timeout: Duration) -> Self {
62        self.timeout = Some(timeout);
63        self
64    }
65
66    /// Propagates one caller-owned cooperative cancellation signal.
67    #[must_use]
68    pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
69        self.cancellation = Some(cancellation);
70        self
71    }
72}
73
74/// A thread-safe caller signal translated to a lane-local Kernel cancellation token.
75#[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    /// Creates a signal that has not been cancelled.
89    pub fn new() -> Self {
90        Self::default()
91    }
92
93    /// Requests cooperative cancellation of attached invocations.
94    pub fn cancel(&self) {
95        self.cancelled.send_replace(true);
96    }
97
98    /// Returns whether cancellation was requested.
99    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/// Generated native values registered for zero-serialization transfer between Kernel lanes.
235#[derive(Clone, Debug, Default)]
236pub struct CrossLaneTransferCatalog {
237    requests: CrossLaneRequestCatalog,
238    interactions: CrossLaneInteractionCatalog,
239}
240
241impl CrossLaneTransferCatalog {
242    /// Creates an empty catalog for a Plan whose bindings remain on one lane.
243    pub fn new() -> Self {
244        Self::default()
245    }
246
247    /// Registers one generated request Capability whose values are `Send`.
248    #[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    /// Registers one generated stream Capability whose values are `Send`.
261    #[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    /// Registers one generated ephemeral Event Capability whose values are `Send`.
274    #[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
299/// One native App executed as a fixed set of Plan-declared single-owner Kernel lanes.
300pub struct ReplicatedNativeApp {
301    plan: Arc<ResolvedAppPlan>,
302    lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
303    diagnostics: Arc<LaneDiagnosticsState>,
304    terminal: Arc<ReplicatedTerminalState>,
305    epoch: Instant,
306}
307
308impl fmt::Debug for ReplicatedNativeApp {
309    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
310        formatter
311            .debug_struct("ReplicatedNativeApp")
312            .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
313            .finish_non_exhaustive()
314    }
315}
316
317impl ReplicatedNativeApp {
318    fn ensure_running(&self) -> Result<(), RuntimeFailure> {
319        if let Some(failure) = self.terminal.failure() {
320            return Err(RuntimeFailure::Internal {
321                detail: failure.to_string(),
322            });
323        }
324        Ok(())
325    }
326
327    /// Starts one unmodified Kernel replica per declared Execution Lane.
328    pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
329    where
330        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
331    {
332        Self::start_with_transfer_catalog(plan, adapters, CrossLaneTransferCatalog::new())
333    }
334
335    /// Starts replicated lanes with generated request types allowed to cross them.
336    #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
337    pub fn start_with_transfers<F>(
338        plan: ResolvedAppPlan,
339        adapters: F,
340        transfers: CrossLaneRequestCatalog,
341    ) -> Result<Self, ReplicatedRunnerError>
342    where
343        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
344    {
345        Self::start_with_transfer_catalog(plan, adapters, transfers.into())
346    }
347
348    /// Starts replicated lanes with generated values allowed to cross them without serialization.
349    #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
350    pub fn start_with_transfer_catalog<F>(
351        plan: ResolvedAppPlan,
352        adapters: F,
353        transfers: CrossLaneTransferCatalog,
354    ) -> Result<Self, ReplicatedRunnerError>
355    where
356        F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
357    {
358        plan.validate()
359            .map_err(|error| ReplicatedRunnerError::InvalidPlan {
360                detail: error.to_string(),
361            })?;
362        transfers.validate_plan(&plan)?;
363        let plan = Arc::new(plan);
364        let adapters = Arc::new(adapters);
365        let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
366        let terminal = Arc::new(ReplicatedTerminalState::default());
367        let epoch = Instant::now();
368        let mut receivers = BTreeMap::new();
369        let senders = plan
370            .execution_lanes()
371            .iter()
372            .map(|lane| {
373                let (sender, receiver) = mpsc::channel(64);
374                receivers.insert(lane.id().clone(), receiver);
375                (lane.id().clone(), sender)
376            })
377            .collect::<BTreeMap<_, _>>();
378        let routes = Arc::new(
379            senders
380                .iter()
381                .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
382                .collect::<BTreeMap<_, _>>(),
383        );
384        let projected = plan
385            .execution_lanes()
386            .iter()
387            .map(|lane| {
388                project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
389            })
390            .collect::<Result<Vec<_>, _>>()?;
391        let mut lanes = BTreeMap::new();
392        let mut startups = Vec::new();
393
394        for (lane_id, lane_plan) in projected {
395            let commands = senders
396                .get(&lane_id)
397                .expect("every declared lane has a command route")
398                .clone();
399            let receiver = receivers
400                .remove(&lane_id)
401                .expect("every declared lane has one command receiver");
402            let (shutdown, shutdown_request) = oneshot::channel();
403            let (started, startup) = std_mpsc::sync_channel(1);
404            let lane_adapters = Arc::clone(&adapters);
405            let lane_diagnostics = Arc::clone(&diagnostics);
406            let lane_terminal = Arc::clone(&terminal);
407            let proxy_adapter = LaneProxyAdapter::new(
408                Arc::clone(&plan),
409                transfers.clone(),
410                Arc::clone(&routes),
411                epoch,
412            );
413            let thread_lane = lane_id.clone();
414            let lane_thread = match thread::Builder::new()
415                .name(format!("lenso-lane-{}", lane_id.as_str()))
416                .spawn(move || {
417                    let reported_lane = thread_lane.clone();
418                    let result = catch_unwind(AssertUnwindSafe(|| {
419                        run_lane(
420                            thread_lane,
421                            lane_plan,
422                            receiver,
423                            shutdown_request,
424                            started,
425                            lane_adapters,
426                            proxy_adapter,
427                            lane_diagnostics,
428                            Arc::clone(&lane_terminal),
429                            epoch,
430                        );
431                    }));
432                    if result.is_err() {
433                        lane_terminal.fail(ReplicatedRunnerError::LanePanicked {
434                            lane: reported_lane.to_string(),
435                        });
436                    }
437                }) {
438                Ok(thread) => thread,
439                Err(error) => {
440                    drop(receivers);
441                    drop(routes);
442                    drop(senders);
443                    terminal.begin_shutdown();
444                    stop_lanes(lanes);
445                    return Err(ReplicatedRunnerError::LaneStartup {
446                        lane: lane_id.to_string(),
447                        detail: error.to_string(),
448                    });
449                }
450            };
451            startups.push((lane_id.clone(), startup));
452            lanes.insert(
453                lane_id.clone(),
454                LaneHandle {
455                    id: lane_id,
456                    commands,
457                    shutdown,
458                    thread: lane_thread,
459                },
460            );
461        }
462
463        for (lane, startup) in startups {
464            match startup.recv() {
465                Ok(Ok(())) => {}
466                Ok(Err(detail)) => {
467                    drop(routes);
468                    drop(senders);
469                    terminal.begin_shutdown();
470                    stop_lanes(lanes);
471                    return Err(ReplicatedRunnerError::LaneStartup {
472                        lane: lane.to_string(),
473                        detail,
474                    });
475                }
476                Err(_) => {
477                    drop(routes);
478                    drop(senders);
479                    let failure = terminal.failure().unwrap_or_else(|| {
480                        ReplicatedRunnerError::LaneUnavailable {
481                            lane: lane.to_string(),
482                        }
483                    });
484                    terminal.begin_shutdown();
485                    stop_lanes(lanes);
486                    return Err(failure);
487                }
488            }
489        }
490
491        Ok(Self {
492            plan,
493            lanes,
494            diagnostics,
495            terminal,
496            epoch,
497        })
498    }
499
500    /// Returns the fixed number of Kernel replicas started from the Plan.
501    pub fn lane_count(&self) -> usize {
502        self.lanes.len()
503    }
504
505    /// Returns structural evidence for placement decisions without exposing payloads.
506    pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
507        self.diagnostics.snapshot()
508    }
509
510    /// Returns whether any Kernel lane reached a terminal failure.
511    pub fn is_failed(&self) -> bool {
512        self.terminal.is_failed()
513    }
514
515    /// Returns the first App-terminal lane failure, when one has occurred.
516    pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
517        self.terminal.failure()
518    }
519
520    /// Waits until one lane makes the replicated App terminal.
521    pub async fn wait_for_terminal(&self) -> ReplicatedRunnerError {
522        self.terminal.wait().await
523    }
524
525    fn resolve_request_lane<C: RequestCapability>(
526        &self,
527        caller_instance: &str,
528    ) -> Result<(&LaneHandle, Option<CrossLaneDiagnostics>), RuntimeFailure> {
529        let binding = singular_binding::<C>(&self.plan, caller_instance)?;
530        let consumer = self.plan.module_instance(caller_instance).ok_or_else(|| {
531            RuntimeFailure::InvalidResolvedPlan {
532                detail: format!("binding consumer `{caller_instance}` is absent from the Plan"),
533            }
534        })?;
535        let provider = self
536            .plan
537            .module_instance(binding.provider_instance())
538            .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
539                detail: format!(
540                    "binding provider `{}` is absent from the Plan",
541                    binding.provider_instance()
542                ),
543            })?;
544        let lane =
545            self.lanes
546                .get(provider.execution_lane())
547                .ok_or_else(|| RuntimeFailure::Internal {
548                    detail: format!(
549                        "Execution Lane `{}` is unavailable",
550                        provider.execution_lane()
551                    ),
552                })?;
553        let diagnostics = (consumer.execution_lane() != provider.execution_lane()).then(|| {
554            (
555                Arc::clone(&self.diagnostics),
556                consumer.execution_lane().clone(),
557                binding.provider_instance().to_owned(),
558            )
559        });
560        Ok((lane, diagnostics))
561    }
562
563    /// Invokes one generated request Capability on its Plan-placed provider lane.
564    pub async fn invoke<C: RequestCapability>(
565        &self,
566        caller_instance: &str,
567        operation: &str,
568        request: C::Request,
569    ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
570    where
571        C::Request: Send,
572        C::Response: Send,
573        C::DomainError: Send,
574    {
575        self.invoke_with_options::<C>(
576            caller_instance,
577            operation,
578            request,
579            LaneInvocationOptions::new(),
580        )
581        .await
582    }
583
584    /// Invokes one generated request with Driver-relative controls on its provider lane.
585    ///
586    /// A deadline or cancellation observed before the provider Kernel allocates
587    /// an invocation identity reports request ID zero.
588    pub async fn invoke_with_options<C: RequestCapability>(
589        &self,
590        caller_instance: &str,
591        operation: &str,
592        request: C::Request,
593        options: LaneInvocationOptions,
594    ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
595    where
596        C::Request: Send,
597        C::Response: Send,
598        C::DomainError: Send,
599    {
600        self.ensure_running()?;
601        // An invocation entering through the Runner can start on the resolved provider owner.
602        // Calls originating inside a Module still use the projected cross-lane transfer endpoint.
603        let (lane, cross_lane_diagnostics) = self.resolve_request_lane::<C>(caller_instance)?;
604        let caller_instance = caller_instance.to_owned();
605        let operation = operation.to_owned();
606        let deadline = options
607            .timeout
608            .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
609        let admission_timeout = options.timeout;
610        let admission_cancellation = options.cancellation.clone();
611        let controlled = deadline.is_some() || admission_cancellation.is_some();
612        let (completed, completion) = oneshot::channel();
613        let (started, start) = if controlled {
614            let (started, start) = oneshot::channel();
615            (Some(started), Some(start))
616        } else {
617            (None, None)
618        };
619        let task = Box::new(move |lane: LaneRuntime| {
620            if let Some((diagnostics, caller_lane, provider_instance)) = cross_lane_diagnostics {
621                diagnostics.record_invocation(&caller_lane, &caller_instance, &provider_instance);
622            }
623            tokio::task::spawn_local(async move {
624                let handle = match lane.request_handle::<C>(&caller_instance) {
625                    Ok(handle) => handle,
626                    Err(error) => {
627                        let _ = completed.send(Err(error));
628                        return;
629                    }
630                };
631                let cancellation = CancellationToken::new();
632                let external_cancellation = options.cancellation;
633                if external_cancellation
634                    .as_ref()
635                    .is_some_and(LaneCancellationToken::is_cancelled)
636                {
637                    cancellation.cancel();
638                }
639                let invocation = if deadline.is_some() || external_cancellation.is_some() {
640                    let context = lane.app.invocation_context(deadline, cancellation.clone());
641                    if let Some(started) = started {
642                        let _ = started.send(());
643                    }
644                    Either::Left(handle.invoke_with_context(&operation, context, request))
645                } else {
646                    Either::Right(handle.invoke(&operation, request))
647                };
648                tokio::pin!(invocation);
649                let result = if let Some(external_cancellation) = external_cancellation {
650                    tokio::select! {
651                        result = &mut invocation => result,
652                        () = external_cancellation.cancelled() => {
653                            cancellation.cancel();
654                            invocation.await
655                        }
656                    }
657                } else {
658                    invocation.await
659                };
660                let _ = completed.send(result);
661            });
662        });
663        if let Some(start) = start {
664            return admission::dispatch_controlled(
665                &lane.id,
666                &lane.commands,
667                task,
668                start,
669                completion,
670                admission_timeout,
671                admission_cancellation,
672            )
673            .await?;
674        }
675        lane.commands
676            .send(task)
677            .await
678            .map_err(|_| RuntimeFailure::Internal {
679                detail: format!("Execution Lane `{}` is unavailable", lane.id),
680            })?;
681        completion.await.map_err(|_| RuntimeFailure::Internal {
682            detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
683        })?
684    }
685
686    /// Stops every Kernel replica with the same bounded shutdown timeout.
687    pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
688        self.terminal.begin_shutdown();
689        let mut completions = Vec::new();
690        let mut threads = Vec::new();
691        let mut first_error = self.terminal.failure();
692        for (_, lane) in self.lanes {
693            let LaneHandle {
694                id,
695                commands,
696                shutdown,
697                thread,
698            } = lane;
699            let (completed, completion) = oneshot::channel();
700            if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
701                completions.push((id.clone(), completion));
702            } else if first_error.is_none() {
703                first_error = Some(ReplicatedRunnerError::LaneUnavailable {
704                    lane: id.to_string(),
705                });
706            }
707            drop(commands);
708            threads.push((id, thread));
709        }
710
711        for (lane, completion) in completions {
712            match completion.await {
713                Ok(ShutdownOutcome::Clean) => {}
714                Ok(outcome) if first_error.is_none() => {
715                    first_error = Some(ReplicatedRunnerError::LaneShutdown {
716                        lane: lane.to_string(),
717                        outcome,
718                    });
719                }
720                Err(_) if first_error.is_none() => {
721                    first_error = Some(ReplicatedRunnerError::LaneUnavailable {
722                        lane: lane.to_string(),
723                    });
724                }
725                _ => {}
726            }
727        }
728        for (lane, thread) in threads {
729            if thread.join().is_err() && first_error.is_none() {
730                first_error = Some(ReplicatedRunnerError::LanePanicked {
731                    lane: lane.to_string(),
732                });
733            }
734        }
735        match first_error {
736            Some(error) => Err(error),
737            None => Ok(()),
738        }
739    }
740}
741
742fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
743    let mut threads = Vec::new();
744    for (_, lane) in lanes {
745        let (completed, _) = oneshot::channel();
746        let _ = lane.shutdown.send(LaneShutdown {
747            timeout: Duration::from_secs(1),
748            completed,
749        });
750        threads.push(lane.thread);
751    }
752    for thread in threads {
753        let _ = thread.join();
754    }
755}
756
757fn singular_binding<'a, C: RequestCapability>(
758    plan: &'a ResolvedAppPlan,
759    caller_instance: &str,
760) -> Result<&'a CapabilityBinding, RuntimeFailure> {
761    let mut bindings = plan.capability_bindings().iter().filter(|binding| {
762        binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
763    });
764    let Some(binding) = bindings.next() else {
765        return Err(RuntimeFailure::Unavailable { capability: C::ID });
766    };
767    let providers = 1 + bindings.count();
768    if providers == 1 {
769        Ok(binding)
770    } else {
771        Err(RuntimeFailure::AmbiguousBinding {
772            capability: C::ID,
773            providers,
774        })
775    }
776}
777
778#[allow(clippy::too_many_arguments)]
779fn run_lane<F>(
780    lane: ExecutionLaneId,
781    plan: ResolvedAppPlan,
782    mut commands: mpsc::Receiver<LaneTask>,
783    mut shutdown: oneshot::Receiver<LaneShutdown>,
784    started: std_mpsc::SyncSender<Result<(), String>>,
785    adapters: Arc<F>,
786    proxy_adapter: LaneProxyAdapter,
787    diagnostics: Arc<LaneDiagnosticsState>,
788    terminal: Arc<ReplicatedTerminalState>,
789    epoch: Instant,
790) where
791    F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
792{
793    let runtime = match tokio::runtime::Builder::new_current_thread()
794        .enable_all()
795        .build()
796    {
797        Ok(runtime) => runtime,
798        Err(error) => {
799            let _ = started.send(Err(error.to_string()));
800            return;
801        }
802    };
803    let local = tokio::task::LocalSet::new();
804    local.block_on(&runtime, async move {
805        let cpu_started = ThreadTime::now();
806        let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
807            Ok(catalog) => catalog,
808            Err(error) => {
809                let _ = started.send(Err(error.to_string()));
810                return;
811            }
812        };
813        let driver = TokioDriver::with_epoch(epoch);
814        let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
815            LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
816        ));
817        let app = match lenso_kernel::Kernel::start_with_diagnostics(
818            plan,
819            driver,
820            catalog,
821            runtime_diagnostics,
822        )
823        .await
824        {
825            Ok(app) => app,
826            Err(error) => {
827                let _ = started.send(Err(format!("{error:?}")));
828                return;
829            }
830        };
831        let lane_runtime = LaneRuntime::new(app.clone());
832        let _ = started.send(Ok(()));
833        diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
834        let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
835        let terminal_monitor = tokio::task::spawn_local(monitor_lane_failure(
836            lane.clone(),
837            app.clone(),
838            Arc::clone(&terminal),
839        ));
840        let terminal_failure = terminal.wait();
841        tokio::pin!(terminal_failure);
842
843        loop {
844            tokio::select! {
845                biased;
846                shutdown = &mut shutdown => {
847                    terminal_monitor.abort();
848                    match shutdown {
849                        Ok(LaneShutdown { timeout, completed }) => {
850                            let outcome = app.shutdown(timeout).await;
851                            let _ = completed.send(outcome);
852                        }
853                        Err(_) => {
854                            let _ = app.shutdown(Duration::from_secs(1)).await;
855                        }
856                    }
857                    break;
858                }
859                _ = &mut terminal_failure => {
860                    terminal_monitor.abort();
861                    let _ = app.shutdown(Duration::from_secs(1)).await;
862                    break;
863                },
864                command = commands.recv() => if let Some(task) = command {
865                    task(lane_runtime.clone());
866                } else {
867                    terminal_monitor.abort();
868                    if !terminal.is_stopping() {
869                        terminal.fail(ReplicatedRunnerError::LaneUnavailable {
870                            lane: lane.to_string(),
871                        });
872                    }
873                    let _ = app.shutdown(Duration::from_secs(1)).await;
874                    break;
875                },
876                _ = sample_interval.tick() => {
877                    diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
878                }
879            }
880        }
881    });
882}
883
884async fn monitor_lane_failure(
885    lane: ExecutionLaneId,
886    app: NativeApp,
887    terminal: Arc<ReplicatedTerminalState>,
888) {
889    let mut failure_interval = tokio::time::interval(Duration::from_millis(10));
890    loop {
891        failure_interval.tick().await;
892        if let Some(error) = app.terminal_failure() {
893            terminal.fail(ReplicatedRunnerError::LaneRuntimeFailure {
894                lane: lane.to_string(),
895                error,
896            });
897            return;
898        }
899        if terminal.is_failed() {
900            return;
901        }
902    }
903}
904
905#[cfg(test)]
906mod tests {
907    use std::time::Duration;
908
909    use lenso_app_plan::{AppComposition, ExecutionLaneId, ExecutionLanePlan};
910    use lenso_kernel::ExecutionAdapterCatalog;
911
912    use super::{ReplicatedNativeApp, ReplicatedRunnerError};
913
914    #[tokio::test(flavor = "current_thread")]
915    async fn one_lane_panic_makes_the_replicated_app_terminal_and_stops_its_peers() {
916        let plan = AppComposition::new(Vec::new(), Vec::new())
917            .with_execution_lanes(vec![
918                ExecutionLanePlan::new("lane-a"),
919                ExecutionLanePlan::new("lane-b"),
920            ])
921            .resolve()
922            .expect("the empty two-lane Plan should resolve");
923        let app = ReplicatedNativeApp::start(plan, |_| ExecutionAdapterCatalog::new())
924            .expect("both empty Kernel lanes should start");
925        let peer_commands = app
926            .lanes
927            .get(&ExecutionLaneId::new("lane-b"))
928            .expect("lane-b should exist")
929            .commands
930            .clone();
931        let flooding =
932            tokio::spawn(
933                async move { while peer_commands.send(Box::new(|_| {})).await.is_ok() {} },
934            );
935        app.lanes
936            .get(&ExecutionLaneId::new("lane-a"))
937            .expect("lane-a should exist")
938            .commands
939            .send(Box::new(|_| panic!("injected lane panic")))
940            .await
941            .expect("lane-a should accept the injected task");
942
943        let failure = tokio::time::timeout(Duration::from_secs(1), app.wait_for_terminal())
944            .await
945            .expect("the lane panic should become terminal promptly");
946        assert_eq!(
947            failure,
948            ReplicatedRunnerError::LanePanicked {
949                lane: "lane-a".to_owned(),
950            }
951        );
952        assert!(app.is_failed());
953        assert_eq!(app.terminal_failure(), Some(failure.clone()));
954        assert_eq!(
955            tokio::time::timeout(Duration::from_secs(1), app.shutdown(Duration::from_secs(1)))
956                .await
957                .expect("a saturated peer lane should still observe terminal failure"),
958            Err(failure)
959        );
960        flooding
961            .await
962            .expect("the peer command producer should stop when the lane closes");
963    }
964}