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
308#[derive(Clone)]
309struct ReplicatedLaneRoute {
310    id: ExecutionLaneId,
311    commands: LaneSender,
312}
313
314/// Cloneable invocation target for one complete replicated App Generation.
315#[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    /// Starts one unmodified Kernel replica per declared Execution Lane.
344    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    /// Starts every declared lane with fallible lane-local Adapter assembly.
352    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    /// Starts every lane and fails closed unless the complete Lane Set is Ready in time.
368    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    /// Starts replicated lanes with generated request types allowed to cross them.
385    #[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    /// Starts replicated lanes with generated values allowed to cross them without serialization.
398    #[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    /// Starts a complete Lane Set with fallible Adapters, transfers, and one Ready deadline.
416    #[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    /// Returns the fixed number of Kernel replicas started from the Plan.
584    pub fn lane_count(&self) -> usize {
585        self.lanes.len()
586    }
587
588    /// Returns structural evidence for placement decisions without exposing payloads.
589    pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
590        self.diagnostics.snapshot()
591    }
592
593    /// Returns whether any Kernel lane reached a terminal failure.
594    pub fn is_failed(&self) -> bool {
595        self.terminal.is_failed()
596    }
597
598    /// Returns the first App-terminal lane failure, when one has occurred.
599    pub fn terminal_failure(&self) -> Option<ReplicatedRunnerError> {
600        self.terminal.failure()
601    }
602
603    /// Waits until one lane makes the replicated App terminal.
604    pub async fn wait_for_terminal(&self) -> ReplicatedRunnerError {
605        self.terminal.wait().await
606    }
607
608    /// Projects a cloneable route which remains pinned by its Generation Lease.
609    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    /// Returns the fixed number of Kernel lanes in this routed Generation.
643    pub fn lane_count(&self) -> usize {
644        self.lanes.len()
645    }
646
647    /// Returns structural evidence for placement decisions without exposing payloads.
648    pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
649        self.diagnostics.snapshot()
650    }
651
652    /// Returns whether any Kernel lane reached a terminal failure.
653    pub fn is_failed(&self) -> bool {
654        self.terminal.is_failed()
655    }
656
657    /// Returns the first App-terminal lane failure, when one has occurred.
658    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    /// Invokes one generated request Capability on its Plan-placed provider lane.
701    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    /// Invokes one generated request with Driver-relative controls on its provider lane.
722    ///
723    /// A deadline or cancellation observed before the provider Kernel allocates
724    /// an invocation identity reports request ID zero.
725    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        // An invocation entering through the Runner can start on the resolved provider owner.
739        // Calls originating inside a Module still use the projected cross-lane transfer endpoint.
740        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    /// Invokes one generated request Capability on its Plan-placed provider lane.
826    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    /// Invokes one generated request with Driver-relative controls.
843    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    /// Stops every Kernel replica with the same bounded shutdown timeout.
861    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}