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, future::LocalBoxFuture};
14use lenso_app_plan::{CapabilityBinding, ExecutionLaneId, ResolvedAppPlan};
15use lenso_kernel::{
16 CancellationToken, DiagnosticEvent, DiagnosticFilter, DiagnosticSource,
17 ExecutionAdapterCatalog, NativeApp, NativeRequestHandle, RequestCapability, RuntimeDiagnostics,
18 RuntimeFailure, ShutdownOutcome,
19};
20use tokio::sync::{mpsc, watch};
21
22use crate::TokioDriver;
23
24mod diagnostics;
25mod projection;
26mod transfer;
27
28pub use diagnostics::LaneDiagnosticsSnapshot;
29use diagnostics::LaneDiagnosticsState;
30use projection::{LaneProxyAdapter, project_lane};
31pub use transfer::CrossLaneRequestCatalog;
32
33const LANE_PROXY_EXECUTION_CLASS: &str = "lenso.native-lane-proxy@1";
34
35#[derive(Clone, Debug, Default)]
37pub struct LaneInvocationOptions {
38 timeout: Option<Duration>,
39 cancellation: Option<LaneCancellationToken>,
40}
41
42impl LaneInvocationOptions {
43 pub const fn new() -> Self {
45 Self {
46 timeout: None,
47 cancellation: None,
48 }
49 }
50
51 #[must_use]
53 pub const fn with_timeout(mut self, timeout: Duration) -> Self {
54 self.timeout = Some(timeout);
55 self
56 }
57
58 #[must_use]
60 pub fn with_cancellation(mut self, cancellation: LaneCancellationToken) -> Self {
61 self.cancellation = Some(cancellation);
62 self
63 }
64}
65
66#[derive(Clone, Debug)]
68pub struct LaneCancellationToken {
69 cancelled: watch::Sender<bool>,
70}
71
72impl Default for LaneCancellationToken {
73 fn default() -> Self {
74 let (cancelled, _) = watch::channel(false);
75 Self { cancelled }
76 }
77}
78
79impl LaneCancellationToken {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn cancel(&self) {
87 self.cancelled.send_replace(true);
88 }
89
90 pub fn is_cancelled(&self) -> bool {
92 *self.cancelled.borrow()
93 }
94
95 async fn cancelled(&self) {
96 let mut cancelled = self.cancelled.subscribe();
97 loop {
98 if *cancelled.borrow_and_update() {
99 return;
100 }
101 if cancelled.changed().await.is_err() {
102 return;
103 }
104 }
105 }
106}
107
108type LaneTask = Box<dyn FnOnce(LaneRuntime) -> LocalBoxFuture<'static, ()> + Send + 'static>;
109type LaneSender = mpsc::Sender<LaneTask>;
110type LaneRoute = mpsc::WeakSender<LaneTask>;
111
112struct LaneShutdown {
113 timeout: Duration,
114 completed: oneshot::Sender<ShutdownOutcome>,
115}
116
117struct LaneHandle {
118 id: ExecutionLaneId,
119 commands: LaneSender,
120 shutdown: oneshot::Sender<LaneShutdown>,
121 thread: thread::JoinHandle<()>,
122}
123
124type TypedRequestHandles = HashMap<String, Box<dyn Any>>;
125
126#[derive(Clone)]
127struct LaneRuntime {
128 app: NativeApp,
129 request_handles: Rc<RefCell<HashMap<TypeId, TypedRequestHandles>>>,
130}
131
132impl LaneRuntime {
133 fn new(app: NativeApp) -> Self {
134 Self {
135 app,
136 request_handles: Rc::new(RefCell::new(HashMap::new())),
137 }
138 }
139
140 fn request_handle<C: RequestCapability>(
141 &self,
142 caller_instance: &str,
143 ) -> Result<Rc<NativeRequestHandle<C>>, RuntimeFailure> {
144 let capability = TypeId::of::<C>();
145 if let Some(handle) = self
146 .request_handles
147 .borrow()
148 .get(&capability)
149 .and_then(|handles| handles.get(caller_instance))
150 .and_then(|handle| handle.downcast_ref::<Rc<NativeRequestHandle<C>>>())
151 {
152 return Ok(handle.clone());
153 }
154 let handle = Rc::new(self.app.handle::<C>(caller_instance)?);
155 self.request_handles
156 .borrow_mut()
157 .entry(capability)
158 .or_default()
159 .insert(caller_instance.to_owned(), Box::new(handle.clone()));
160 Ok(handle)
161 }
162}
163
164#[derive(Clone, Debug, Eq, PartialEq)]
166pub enum ReplicatedRunnerError {
167 InvalidPlan { detail: String },
169 LaneStartup { lane: String, detail: String },
171 MissingCrossLaneRequestTransfer { capability: String },
173 LaneUnavailable { lane: String },
175 LanePanicked { lane: String },
177 LaneShutdown {
179 lane: String,
180 outcome: ShutdownOutcome,
181 },
182}
183
184impl fmt::Display for ReplicatedRunnerError {
185 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
186 match self {
187 Self::InvalidPlan { detail } => {
188 write!(formatter, "invalid Resolved App Plan: {detail}")
189 }
190 Self::LaneStartup { lane, detail } => {
191 write!(
192 formatter,
193 "Execution Lane `{lane}` failed to start: {detail}"
194 )
195 }
196 Self::MissingCrossLaneRequestTransfer { capability } => write!(
197 formatter,
198 "Capability `{capability}` has no registered native cross-lane request transfer"
199 ),
200 Self::LaneUnavailable { lane } => {
201 write!(formatter, "Execution Lane `{lane}` is unavailable")
202 }
203 Self::LanePanicked { lane } => write!(formatter, "Execution Lane `{lane}` panicked"),
204 Self::LaneShutdown { lane, outcome } => write!(
205 formatter,
206 "Execution Lane `{lane}` stopped with {outcome:?}"
207 ),
208 }
209 }
210}
211
212impl std::error::Error for ReplicatedRunnerError {}
213
214pub struct ReplicatedNativeApp {
216 plan: Arc<ResolvedAppPlan>,
217 lanes: BTreeMap<ExecutionLaneId, LaneHandle>,
218 diagnostics: Arc<LaneDiagnosticsState>,
219 epoch: Instant,
220}
221
222impl fmt::Debug for ReplicatedNativeApp {
223 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
224 formatter
225 .debug_struct("ReplicatedNativeApp")
226 .field("lanes", &self.lanes.keys().collect::<Vec<_>>())
227 .finish_non_exhaustive()
228 }
229}
230
231impl ReplicatedNativeApp {
232 pub fn start<F>(plan: ResolvedAppPlan, adapters: F) -> Result<Self, ReplicatedRunnerError>
234 where
235 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
236 {
237 Self::start_with_transfers(plan, adapters, CrossLaneRequestCatalog::new())
238 }
239
240 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 Box::pin(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
577fn run_lane<F>(
578 lane: ExecutionLaneId,
579 plan: ResolvedAppPlan,
580 mut commands: mpsc::Receiver<LaneTask>,
581 mut shutdown: oneshot::Receiver<LaneShutdown>,
582 started: std_mpsc::SyncSender<Result<(), String>>,
583 adapters: Arc<F>,
584 proxy_adapter: LaneProxyAdapter,
585 diagnostics: Arc<LaneDiagnosticsState>,
586 epoch: Instant,
587) where
588 F: Fn(&ExecutionLaneId) -> ExecutionAdapterCatalog + Send + Sync + 'static,
589{
590 let runtime = match tokio::runtime::Builder::new_current_thread()
591 .enable_all()
592 .build()
593 {
594 Ok(runtime) => runtime,
595 Err(error) => {
596 let _ = started.send(Err(error.to_string()));
597 return;
598 }
599 };
600 let local = tokio::task::LocalSet::new();
601 local.block_on(&runtime, async move {
602 let cpu_started = ThreadTime::now();
603 let catalog = match adapters(&lane).with_adapter(proxy_adapter) {
604 Ok(catalog) => catalog,
605 Err(error) => {
606 let _ = started.send(Err(error.to_string()));
607 return;
608 }
609 };
610 let driver = TokioDriver::with_epoch(epoch);
611 let runtime_diagnostics = RuntimeDiagnostics::new();
612 let observer = runtime_diagnostics
613 .subscribe(DiagnosticFilter::only(DiagnosticSource::Invocation), 2048)
614 .expect("diagnostics capacity is positive");
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 tokio::task::spawn_local(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 while let Some(record) = observer.try_recv() {
660 if let DiagnosticEvent::InvocationStarted {
661 caller_instance: Some(caller),
662 provider_instance: Some(provider),
663 ..
664 } = record.event
665 {
666 diagnostics.record_invocation(&lane, &caller, &provider);
667 }
668 }
669 }
670 });
671}