1use std::{
2 any::{Any, TypeId},
3 cell::RefCell,
4 collections::{BTreeMap, HashMap},
5 fmt,
6 rc::Rc,
7 sync::{Arc, mpsc as std_mpsc},
8 thread,
9 time::{Duration, Instant},
10};
11
12use cpu_time::ThreadTime;
13use futures::{channel::oneshot, future::Either};
14use lenso_app_plan::{CapabilityBinding, ExecutionLaneId, ResolvedAppPlan};
15use lenso_kernel::{
16 CancellationToken, ExecutionAdapterCatalog, NativeApp, NativeRequestHandle, RequestCapability,
17 RuntimeDiagnostics, RuntimeFailure, ShutdownOutcome,
18};
19use tokio::sync::{mpsc, watch};
20
21use crate::TokioDriver;
22
23mod diagnostics;
24mod projection;
25mod transfer;
26
27pub use diagnostics::LaneDiagnosticsSnapshot;
28use diagnostics::{LaneDiagnosticsState, LaneInvocationProbe};
29use projection::{LaneProxyAdapter, project_lane};
30pub use transfer::CrossLaneRequestCatalog;
31
32const LANE_PROXY_EXECUTION_CLASS: &str = "lenso.native-lane-proxy@1";
33
34#[derive(Clone, Debug, Default)]
36pub struct LaneInvocationOptions {
37 timeout: Option<Duration>,
38 cancellation: Option<LaneCancellationToken>,
39}
40
41impl LaneInvocationOptions {
42 pub const fn new() -> Self {
44 Self {
45 timeout: None,
46 cancellation: None,
47 }
48 }
49
50 #[must_use]
52 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
53 self.timeout = Some(timeout);
54 self
55 }
56
57 #[must_use]
59 pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
60 self.cancellation = Some(cancellation);
61 self
62 }
63}
64
65#[derive(Clone, Debug)]
67pub struct LaneCancellationToken {
68 cancelled: watch::Sender<bool>,
69}
70
71impl Default for LaneCancellationToken {
72 fn default() -> Self {
73 let (cancelled, _) = watch::channel(false);
74 Self { cancelled }
75 }
76}
77
78impl LaneCancellationToken {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn cancel(&self) {
86 self.cancelled.send_replace(true);
87 }
88
89 pub fn is_cancelled(&self) -> bool {
91 *self.cancelled.borrow()
92 }
93
94 async fn cancelled(&self) {
95 let mut cancelled = self.cancelled.subscribe();
96 loop {
97 if *cancelled.borrow_and_update() {
98 return;
99 }
100 if cancelled.changed().await.is_err() {
101 return;
102 }
103 }
104 }
105}
106
107type LaneTask = Box<dyn FnOnce(LaneRuntime) + Send + 'static>;
108type LaneSender = mpsc::Sender<LaneTask>;
109type LaneRoute = mpsc::WeakSender<LaneTask>;
110
111struct LaneShutdown {
112 timeout: Duration,
113 completed: oneshot::Sender<ShutdownOutcome>,
114}
115
116struct LaneHandle {
117 id: ExecutionLaneId,
118 commands: LaneSender,
119 shutdown: oneshot::Sender<LaneShutdown>,
120 thread: thread::JoinHandle<()>,
121}
122
123type TypedRequestHandles = HashMap<String, Box<dyn Any>>;
124
125#[derive(Clone)]
126struct LaneRuntime {
127 app: NativeApp,
128 request_handles: Rc<RefCell<HashMap<TypeId, TypedRequestHandles>>>,
129}
130
131impl LaneRuntime {
132 fn new(app: NativeApp) -> Self {
133 Self {
134 app,
135 request_handles: Rc::new(RefCell::new(HashMap::new())),
136 }
137 }
138
139 fn request_handle<C: RequestCapability>(
140 &self,
141 caller_instance: &str,
142 ) -> Result<Rc<NativeRequestHandle<C>>, RuntimeFailure> {
143 let capability = TypeId::of::<C>();
144 if let Some(handle) = self
145 .request_handles
146 .borrow()
147 .get(&capability)
148 .and_then(|handles| handles.get(caller_instance))
149 .and_then(|handle| handle.downcast_ref::<Rc<NativeRequestHandle<C>>>())
150 {
151 return Ok(handle.clone());
152 }
153 let handle = Rc::new(self.app.handle::<C>(caller_instance)?);
154 self.request_handles
155 .borrow_mut()
156 .entry(capability)
157 .or_default()
158 .insert(caller_instance.to_owned(), Box::new(handle.clone()));
159 Ok(handle)
160 }
161}
162
163#[derive(Clone, Debug, Eq, PartialEq)]
165pub enum ReplicatedRunnerError {
166 InvalidPlan { detail: String },
168 LaneStartup { lane: String, detail: String },
170 MissingCrossLaneRequestTransfer { capability: String },
172 LaneUnavailable { lane: String },
174 LanePanicked { lane: String },
176 LaneShutdown {
178 lane: String,
179 outcome: ShutdownOutcome,
180 },
181}
182
183impl fmt::Display for ReplicatedRunnerError {
184 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185 match self {
186 Self::InvalidPlan { detail } => {
187 write!(formatter, "invalid Resolved App Plan: {detail}")
188 }
189 Self::LaneStartup { lane, detail } => {
190 write!(
191 formatter,
192 "Execution Lane `{lane}` failed to start: {detail}"
193 )
194 }
195 Self::MissingCrossLaneRequestTransfer { capability } => write!(
196 formatter,
197 "Capability `{capability}` has no registered native cross-lane request transfer"
198 ),
199 Self::LaneUnavailable { lane } => {
200 write!(formatter, "Execution Lane `{lane}` is unavailable")
201 }
202 Self::LanePanicked { lane } => write!(formatter, "Execution Lane `{lane}` panicked"),
203 Self::LaneShutdown { lane, outcome } => write!(
204 formatter,
205 "Execution Lane `{lane}` stopped with {outcome:?}"
206 ),
207 }
208 }
209}
210
211impl std::error::Error for ReplicatedRunnerError {}
212
213pub struct ReplicatedNativeApp {
215 plan: Arc<ResolvedAppPlan>,
216 lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
217 diagnostics: Arc<LaneDiagnosticsState>,
218 epoch: Instant,
219}
220
221impl fmt::Debug for ReplicatedNativeApp {
222 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223 formatter
224 .debug_struct("ReplicatedNativeApp")
225 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
226 .finish_non_exhaustive()
227 }
228}
229
230impl ReplicatedNativeApp {
231 pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
233 where
234 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
235 {
236 Self::start_with_transfers(plan, adapters, CrossLaneRequestCatalog::new())
237 }
238
239 #[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
241 pub fn start_with_transfers<F>(
242 plan: ResolvedAppPlan,
243 adapters: F,
244 transfers: CrossLaneRequestCatalog,
245 ) -> Result<Self, ReplicatedRunnerError>
246 where
247 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
248 {
249 plan.validate()
250 .map_err(|error| ReplicatedRunnerError::InvalidPlan {
251 detail: error.to_string(),
252 })?;
253 transfers.validate_plan(&plan)?;
254 let plan = Arc::new(plan);
255 let adapters = Arc::new(adapters);
256 let diagnostics = Arc::new(LaneDiagnosticsState::new(Arc::clone(&plan)));
257 let epoch = Instant::now();
258 let mut receivers = BTreeMap::new();
259 let senders = plan
260 .execution_lanes()
261 .iter()
262 .map(|lane| {
263 let (sender, receiver) = mpsc::channel(64);
264 receivers.insert(lane.id().clone(), receiver);
265 (lane.id().clone(), sender)
266 })
267 .collect::<BTreeMap<_, _>>();
268 let routes = Arc::new(
269 senders
270 .iter()
271 .map(|(lane, sender)| (lane.clone(), sender.downgrade()))
272 .collect::<BTreeMap<_, _>>(),
273 );
274 let projected = plan
275 .execution_lanes()
276 .iter()
277 .map(|lane| {
278 project_lane(&plan, lane.id()).map(|projected| (lane.id().clone(), projected))
279 })
280 .collect::<Result<Vec<_>, _>>()?;
281 let mut lanes = BTreeMap::new();
282 let mut startups = Vec::new();
283
284 for (lane_id, lane_plan) in projected {
285 let commands = senders
286 .get(&lane_id)
287 .expect("every declared lane has a command route")
288 .clone();
289 let receiver = receivers
290 .remove(&lane_id)
291 .expect("every declared lane has one command receiver");
292 let (shutdown, shutdown_request) = oneshot::channel();
293 let (started, startup) = std_mpsc::sync_channel(1);
294 let lane_adapters = Arc::clone(&adapters);
295 let lane_diagnostics = Arc::clone(&diagnostics);
296 let proxy_adapter = LaneProxyAdapter::new(
297 Arc::clone(&plan),
298 transfers.clone(),
299 Arc::clone(&routes),
300 epoch,
301 );
302 let thread_lane = lane_id.clone();
303 let lane_thread = match thread::Builder::new()
304 .name(format!("lenso-lane-{}", lane_id.as_str()))
305 .spawn(move || {
306 run_lane(
307 thread_lane,
308 lane_plan,
309 receiver,
310 shutdown_request,
311 started,
312 lane_adapters,
313 proxy_adapter,
314 lane_diagnostics,
315 epoch,
316 );
317 }) {
318 Ok(thread) => thread,
319 Err(error) => {
320 drop(receivers);
321 drop(routes);
322 drop(senders);
323 stop_lanes(lanes);
324 return Err(ReplicatedRunnerError::LaneStartup {
325 lane: lane_id.to_string(),
326 detail: error.to_string(),
327 });
328 }
329 };
330 startups.push((lane_id.clone(), startup));
331 lanes.insert(
332 lane_id.clone(),
333 LaneHandle {
334 id: lane_id,
335 commands,
336 shutdown,
337 thread: lane_thread,
338 },
339 );
340 }
341
342 for (lane, startup) in startups {
343 match startup.recv() {
344 Ok(Ok(())) => {}
345 Ok(Err(detail)) => {
346 drop(routes);
347 drop(senders);
348 stop_lanes(lanes);
349 return Err(ReplicatedRunnerError::LaneStartup {
350 lane: lane.to_string(),
351 detail,
352 });
353 }
354 Err(_) => {
355 drop(routes);
356 drop(senders);
357 stop_lanes(lanes);
358 return Err(ReplicatedRunnerError::LaneUnavailable {
359 lane: lane.to_string(),
360 });
361 }
362 }
363 }
364
365 Ok(Self {
366 plan,
367 lanes,
368 diagnostics,
369 epoch,
370 })
371 }
372
373 pub fn lane_count(&self) -> usize {
375 self.lanes.len()
376 }
377
378 pub fn diagnostics_snapshot(&self) -> LaneDiagnosticsSnapshot {
380 self.diagnostics.snapshot()
381 }
382
383 pub async fn invoke<C: RequestCapability>(
385 &self,
386 caller_instance: &str,
387 operation: &str,
388 request: C::Request,
389 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
390 where
391 C::Request: Send,
392 C::Response: Send,
393 C::DomainError: Send,
394 {
395 self.invoke_with_options::<C>(
396 caller_instance,
397 operation,
398 request,
399 LaneInvocationOptions::new(),
400 )
401 .await
402 }
403
404 pub async fn invoke_with_options<C: RequestCapability>(
406 &self,
407 caller_instance: &str,
408 operation: &str,
409 request: C::Request,
410 options: LaneInvocationOptions,
411 ) -> Result<Result<C::Response, C::DomainError>, RuntimeFailure>
412 where
413 C::Request: Send,
414 C::Response: Send,
415 C::DomainError: Send,
416 {
417 let _ = singular_binding::<C>(&self.plan, caller_instance)?;
418 let consumer = self.plan.module_instance(caller_instance).ok_or_else(|| {
419 RuntimeFailure::InvalidResolvedPlan {
420 detail: format!("binding consumer `{caller_instance}` is absent from the Plan"),
421 }
422 })?;
423 let lane =
424 self.lanes
425 .get(consumer.execution_lane())
426 .ok_or_else(|| RuntimeFailure::Internal {
427 detail: format!(
428 "Execution Lane `{}` is unavailable",
429 consumer.execution_lane()
430 ),
431 })?;
432 let caller_instance = caller_instance.to_owned();
433 let operation = operation.to_owned();
434 let deadline = options
435 .timeout
436 .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
437 let (completed, completion) = oneshot::channel();
438 lane.commands
439 .send(Box::new(move |lane| {
440 tokio::task::spawn_local(async move {
441 let handle = match lane.request_handle::<C>(&caller_instance) {
442 Ok(handle) => handle,
443 Err(error) => {
444 let _ = completed.send(Err(error));
445 return;
446 }
447 };
448 let cancellation = CancellationToken::new();
449 let external_cancellation = options.cancellation;
450 if external_cancellation
451 .as_ref()
452 .is_some_and(LaneCancellationToken::is_cancelled)
453 {
454 cancellation.cancel();
455 }
456 let invocation = if deadline.is_some() || external_cancellation.is_some() {
457 let context = lane.app.invocation_context(deadline, cancellation.clone());
458 Either::Left(handle.invoke_with_context(&operation, context, request))
459 } else {
460 Either::Right(handle.invoke(&operation, request))
461 };
462 tokio::pin!(invocation);
463 let result = if let Some(external_cancellation) = external_cancellation {
464 tokio::select! {
465 result = &mut invocation => result,
466 () = external_cancellation.cancelled() => {
467 cancellation.cancel();
468 invocation.await
469 }
470 }
471 } else {
472 invocation.await
473 };
474 let _ = completed.send(result);
475 });
476 }))
477 .await
478 .map_err(|_| RuntimeFailure::Internal {
479 detail: format!("Execution Lane `{}` is unavailable", lane.id),
480 })?;
481 completion.await.map_err(|_| RuntimeFailure::Internal {
482 detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
483 })?
484 }
485
486 pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
488 let mut completions = Vec::new();
489 let mut threads = Vec::new();
490 let mut first_error = None;
491 for (_, lane) in self.lanes {
492 let LaneHandle {
493 id,
494 commands,
495 shutdown,
496 thread,
497 } = lane;
498 let (completed, completion) = oneshot::channel();
499 if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
500 completions.push((id.clone(), completion));
501 } else if first_error.is_none() {
502 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
503 lane: id.to_string(),
504 });
505 }
506 drop(commands);
507 threads.push((id, thread));
508 }
509
510 for (lane, completion) in completions {
511 match completion.await {
512 Ok(ShutdownOutcome::Clean) => {}
513 Ok(outcome) if first_error.is_none() => {
514 first_error = Some(ReplicatedRunnerError::LaneShutdown {
515 lane: lane.to_string(),
516 outcome,
517 });
518 }
519 Err(_) if first_error.is_none() => {
520 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
521 lane: lane.to_string(),
522 });
523 }
524 _ => {}
525 }
526 }
527 for (lane, thread) in threads {
528 if thread.join().is_err() && first_error.is_none() {
529 first_error = Some(ReplicatedRunnerError::LanePanicked {
530 lane: lane.to_string(),
531 });
532 }
533 }
534 match first_error {
535 Some(error) => Err(error),
536 None => Ok(()),
537 }
538 }
539}
540
541fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
542 let mut threads = Vec::new();
543 for (_, lane) in lanes {
544 let (completed, _) = oneshot::channel();
545 let _ = lane.shutdown.send(LaneShutdown {
546 timeout: Duration::from_secs(1),
547 completed,
548 });
549 threads.push(lane.thread);
550 }
551 for thread in threads {
552 let _ = thread.join();
553 }
554}
555
556fn singular_binding<'a, C: RequestCapability>(
557 plan: &'a ResolvedAppPlan,
558 caller_instance: &str,
559) -> Result<&'a CapabilityBinding, RuntimeFailure> {
560 let mut bindings = plan.capability_bindings().iter().filter(|binding| {
561 binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
562 });
563 let Some(binding) = bindings.next() else {
564 return Err(RuntimeFailure::Unavailable { capability: C::ID });
565 };
566 let providers = 1 + bindings.count();
567 if providers == 1 {
568 Ok(binding)
569 } else {
570 Err(RuntimeFailure::AmbiguousBinding {
571 capability: C::ID,
572 providers,
573 })
574 }
575}
576
577#[allow(clippy::too_many_arguments)]
578fn run_lane<F>(
579 lane: ExecutionLaneId,
580 plan: ResolvedAppPlan,
581 mut commands: mpsc::Receiver<LaneTask>,
582 mut shutdown: oneshot::Receiver<LaneShutdown>,
583 started: std_mpsc::SyncSender<Result<(), String>>,
584 adapters: Arc<F>,
585 proxy_adapter: LaneProxyAdapter,
586 diagnostics: Arc<LaneDiagnosticsState>,
587 epoch: Instant,
588) where
589 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
590{
591 let runtime = match tokio::runtime::Builder::new_current_thread()
592 .enable_all()
593 .build()
594 {
595 Ok(runtime) => runtime,
596 Err(error) => {
597 let _ = started.send(Err(error.to_string()));
598 return;
599 }
600 };
601 let local = tokio::task::LocalSet::new();
602 local.block_on(&runtime, async move {
603 let cpu_started = ThreadTime::now();
604 let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
605 Ok(catalog) => catalog,
606 Err(error) => {
607 let _ = started.send(Err(error.to_string()));
608 return;
609 }
610 };
611 let driver = TokioDriver::with_epoch(epoch);
612 let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
613 LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
614 ));
615 let app = match lenso_kernel::Kernel::start_with_diagnostics(
616 plan,
617 driver,
618 catalog,
619 runtime_diagnostics,
620 )
621 .await
622 {
623 Ok(app) => app,
624 Err(error) => {
625 let _ = started.send(Err(format!("{error:?}")));
626 return;
627 }
628 };
629 let lane_runtime = LaneRuntime::new(app.clone());
630 let _ = started.send(Ok(()));
631 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
632 let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
633
634 loop {
635 tokio::select! {
636 biased;
637 shutdown = &mut shutdown => {
638 match shutdown {
639 Ok(LaneShutdown { timeout, completed }) => {
640 let outcome = app.shutdown(timeout).await;
641 let _ = completed.send(outcome);
642 }
643 Err(_) => {
644 let _ = app.shutdown(Duration::from_secs(1)).await;
645 }
646 }
647 break;
648 }
649 command = commands.recv() => if let Some(task) = command {
650 task(lane_runtime.clone());
651 } else {
652 let _ = app.shutdown(Duration::from_secs(1)).await;
653 break;
654 },
655 _ = sample_interval.tick() => {
656 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
657 }
658 }
659 }
660 });
661}