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