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 binding = 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 provider = self
424 .plan
425 .module_instance(binding.provider_instance())
426 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
427 detail: format!(
428 "binding provider `{}` is absent from the Plan",
429 binding.provider_instance()
430 ),
431 })?;
432 let lane =
435 self.lanes
436 .get(provider.execution_lane())
437 .ok_or_else(|| RuntimeFailure::Internal {
438 detail: format!(
439 "Execution Lane `{}` is unavailable",
440 provider.execution_lane()
441 ),
442 })?;
443 let cross_lane_diagnostics =
444 (consumer.execution_lane() != provider.execution_lane()).then(|| {
445 (
446 Arc::clone(&self.diagnostics),
447 consumer.execution_lane().clone(),
448 binding.provider_instance().to_owned(),
449 )
450 });
451 let caller_instance = caller_instance.to_owned();
452 let operation = operation.to_owned();
453 let deadline = options
454 .timeout
455 .map(|timeout| self.epoch.elapsed().saturating_add(timeout));
456 let (completed, completion) = oneshot::channel();
457 lane.commands
458 .send(Box::new(move |lane| {
459 if let Some((diagnostics, caller_lane, provider_instance)) = cross_lane_diagnostics
460 {
461 diagnostics.record_invocation(
462 &caller_lane,
463 &caller_instance,
464 &provider_instance,
465 );
466 }
467 tokio::task::spawn_local(async move {
468 let handle = match lane.request_handle::<C>(&caller_instance) {
469 Ok(handle) => handle,
470 Err(error) => {
471 let _ = completed.send(Err(error));
472 return;
473 }
474 };
475 let cancellation = CancellationToken::new();
476 let external_cancellation = options.cancellation;
477 if external_cancellation
478 .as_ref()
479 .is_some_and(LaneCancellationToken::is_cancelled)
480 {
481 cancellation.cancel();
482 }
483 let invocation = if deadline.is_some() || external_cancellation.is_some() {
484 let context = lane.app.invocation_context(deadline, cancellation.clone());
485 Either::Left(handle.invoke_with_context(&operation, context, request))
486 } else {
487 Either::Right(handle.invoke(&operation, request))
488 };
489 tokio::pin!(invocation);
490 let result = if let Some(external_cancellation) = external_cancellation {
491 tokio::select! {
492 result = &mut invocation => result,
493 () = external_cancellation.cancelled() => {
494 cancellation.cancel();
495 invocation.await
496 }
497 }
498 } else {
499 invocation.await
500 };
501 let _ = completed.send(result);
502 });
503 }))
504 .await
505 .map_err(|_| RuntimeFailure::Internal {
506 detail: format!("Execution Lane `{}` is unavailable", lane.id),
507 })?;
508 completion.await.map_err(|_| RuntimeFailure::Internal {
509 detail: format!("Execution Lane `{}` dropped an invocation", lane.id),
510 })?
511 }
512
513 pub async fn shutdown(self, timeout: Duration) -> Result<(), ReplicatedRunnerError> {
515 let mut completions = Vec::new();
516 let mut threads = Vec::new();
517 let mut first_error = None;
518 for (_, lane) in self.lanes {
519 let LaneHandle {
520 id,
521 commands,
522 shutdown,
523 thread,
524 } = lane;
525 let (completed, completion) = oneshot::channel();
526 if shutdown.send(LaneShutdown { timeout, completed }).is_ok() {
527 completions.push((id.clone(), completion));
528 } else if first_error.is_none() {
529 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
530 lane: id.to_string(),
531 });
532 }
533 drop(commands);
534 threads.push((id, thread));
535 }
536
537 for (lane, completion) in completions {
538 match completion.await {
539 Ok(ShutdownOutcome::Clean) => {}
540 Ok(outcome) if first_error.is_none() => {
541 first_error = Some(ReplicatedRunnerError::LaneShutdown {
542 lane: lane.to_string(),
543 outcome,
544 });
545 }
546 Err(_) if first_error.is_none() => {
547 first_error = Some(ReplicatedRunnerError::LaneUnavailable {
548 lane: lane.to_string(),
549 });
550 }
551 _ => {}
552 }
553 }
554 for (lane, thread) in threads {
555 if thread.join().is_err() && first_error.is_none() {
556 first_error = Some(ReplicatedRunnerError::LanePanicked {
557 lane: lane.to_string(),
558 });
559 }
560 }
561 match first_error {
562 Some(error) => Err(error),
563 None => Ok(()),
564 }
565 }
566}
567
568fn stop_lanes(lanes: BTreeMap<ExecutionLaneId, LaneHandle>) {
569 let mut threads = Vec::new();
570 for (_, lane) in lanes {
571 let (completed, _) = oneshot::channel();
572 let _ = lane.shutdown.send(LaneShutdown {
573 timeout: Duration::from_secs(1),
574 completed,
575 });
576 threads.push(lane.thread);
577 }
578 for thread in threads {
579 let _ = thread.join();
580 }
581}
582
583fn singular_binding<'a, C: RequestCapability>(
584 plan: &'a ResolvedAppPlan,
585 caller_instance: &str,
586) -> Result<&'a CapabilityBinding, RuntimeFailure> {
587 let mut bindings = plan.capability_bindings().iter().filter(|binding| {
588 binding.consumer_instance() == caller_instance && binding.capability_id() == C::ID
589 });
590 let Some(binding) = bindings.next() else {
591 return Err(RuntimeFailure::Unavailable { capability: C::ID });
592 };
593 let providers = 1 + bindings.count();
594 if providers == 1 {
595 Ok(binding)
596 } else {
597 Err(RuntimeFailure::AmbiguousBinding {
598 capability: C::ID,
599 providers,
600 })
601 }
602}
603
604#[allow(clippy::too_many_arguments)]
605fn run_lane<F>(
606 lane: ExecutionLaneId,
607 plan: ResolvedAppPlan,
608 mut commands: mpsc::Receiver<LaneTask>,
609 mut shutdown: oneshot::Receiver<LaneShutdown>,
610 started: std_mpsc::SyncSender<Result<(), String>>,
611 adapters: Arc<F>,
612 proxy_adapter: LaneProxyAdapter,
613 diagnostics: Arc<LaneDiagnosticsState>,
614 epoch: Instant,
615) where
616 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
617{
618 let runtime = match tokio::runtime::Builder::new_current_thread()
619 .enable_all()
620 .build()
621 {
622 Ok(runtime) => runtime,
623 Err(error) => {
624 let _ = started.send(Err(error.to_string()));
625 return;
626 }
627 };
628 let local = tokio::task::LocalSet::new();
629 local.block_on(&runtime, async move {
630 let cpu_started = ThreadTime::now();
631 let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
632 Ok(catalog) => catalog,
633 Err(error) => {
634 let _ = started.send(Err(error.to_string()));
635 return;
636 }
637 };
638 let driver = TokioDriver::with_epoch(epoch);
639 let runtime_diagnostics = RuntimeDiagnostics::new().with_invocation_probe(Rc::new(
640 LaneInvocationProbe::new(Arc::clone(&diagnostics), lane.clone()),
641 ));
642 let app = match lenso_kernel::Kernel::start_with_diagnostics(
643 plan,
644 driver,
645 catalog,
646 runtime_diagnostics,
647 )
648 .await
649 {
650 Ok(app) => app,
651 Err(error) => {
652 let _ = started.send(Err(format!("{error:?}")));
653 return;
654 }
655 };
656 let lane_runtime = LaneRuntime::new(app.clone());
657 let _ = started.send(Ok(()));
658 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
659 let mut sample_interval = tokio::time::interval(Duration::from_millis(10));
660
661 loop {
662 tokio::select! {
663 biased;
664 shutdown = &mut shutdown => {
665 match shutdown {
666 Ok(LaneShutdown { timeout, completed }) => {
667 let outcome = app.shutdown(timeout).await;
668 let _ = completed.send(outcome);
669 }
670 Err(_) => {
671 let _ = app.shutdown(Duration::from_secs(1)).await;
672 }
673 }
674 break;
675 }
676 command = commands.recv() => if let Some(task) = command {
677 task(lane_runtime.clone());
678 } else {
679 let _ = app.shutdown(Duration::from_secs(1)).await;
680 break;
681 },
682 _ = sample_interval.tick() => {
683 diagnostics.publish_lane(&lane, &app, cpu_started.elapsed());
684 }
685 }
686 }
687 });
688}